mindcache 3.4.3 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/{CloudAdapter-CJS3Sh4f.d.mts → CloudAdapter-CM7nyJaG.d.mts} +85 -25
- package/dist/{CloudAdapter-CJS3Sh4f.d.ts → CloudAdapter-CM7nyJaG.d.ts} +85 -25
- package/dist/cloud/index.d.mts +2 -2
- package/dist/cloud/index.d.ts +2 -2
- package/dist/cloud/index.js +1105 -729
- package/dist/cloud/index.js.map +1 -1
- package/dist/cloud/index.mjs +1105 -729
- package/dist/cloud/index.mjs.map +1 -1
- package/dist/index.d.mts +125 -3
- package/dist/index.d.ts +125 -3
- package/dist/index.js +1527 -832
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1526 -833
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +1105 -731
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +1105 -731
- package/dist/server.mjs.map +1 -1
- package/docs/mindcache-api.md +148 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -471,12 +471,872 @@ var SystemTagHelpers = {
|
|
|
471
471
|
isLLMReadable: (attrs) => attrs.systemTags.includes("SystemPrompt") || attrs.systemTags.includes("LLMRead"),
|
|
472
472
|
/** Check if key is included in system prompt */
|
|
473
473
|
isInSystemPrompt: (attrs) => attrs.systemTags.includes("SystemPrompt"),
|
|
474
|
-
/** Check if key is protected from deletion */
|
|
475
|
-
isProtected: (attrs) => attrs.systemTags.includes("protected"),
|
|
476
474
|
/** Check if key uses template injection */
|
|
477
475
|
hasTemplateInjection: (attrs) => attrs.systemTags.includes("ApplyTemplate")
|
|
478
476
|
};
|
|
479
477
|
|
|
478
|
+
// src/core/MarkdownSerializer.ts
|
|
479
|
+
var MarkdownSerializer = class {
|
|
480
|
+
/**
|
|
481
|
+
* Export MindCache data to Markdown format.
|
|
482
|
+
*/
|
|
483
|
+
static toMarkdown(mc) {
|
|
484
|
+
const now = /* @__PURE__ */ new Date();
|
|
485
|
+
const lines = [];
|
|
486
|
+
const appendixEntries = [];
|
|
487
|
+
let appendixCounter = 0;
|
|
488
|
+
lines.push("# MindCache STM Export");
|
|
489
|
+
lines.push("");
|
|
490
|
+
lines.push(`Export Date: ${now.toISOString().split("T")[0]}`);
|
|
491
|
+
lines.push("");
|
|
492
|
+
lines.push("---");
|
|
493
|
+
lines.push("");
|
|
494
|
+
lines.push("## STM Entries");
|
|
495
|
+
lines.push("");
|
|
496
|
+
const sortedKeys = mc.getSortedKeys();
|
|
497
|
+
sortedKeys.forEach((key) => {
|
|
498
|
+
const attributes = mc.get_attributes(key);
|
|
499
|
+
const value = mc.get_value(key);
|
|
500
|
+
lines.push(`### ${key}`);
|
|
501
|
+
const entryType = attributes?.type || "text";
|
|
502
|
+
lines.push(`- **Type**: \`${entryType}\``);
|
|
503
|
+
lines.push(`- **System Tags**: \`${attributes?.systemTags?.join(", ") || "none"}\``);
|
|
504
|
+
lines.push(`- **Z-Index**: \`${attributes?.zIndex ?? 0}\``);
|
|
505
|
+
if (attributes?.contentTags && attributes.contentTags.length > 0) {
|
|
506
|
+
lines.push(`- **Tags**: \`${attributes.contentTags.join("`, `")}\``);
|
|
507
|
+
}
|
|
508
|
+
if (attributes?.contentType) {
|
|
509
|
+
lines.push(`- **Content Type**: \`${attributes.contentType}\``);
|
|
510
|
+
}
|
|
511
|
+
if (entryType === "image" || entryType === "file") {
|
|
512
|
+
const label = String.fromCharCode(65 + appendixCounter);
|
|
513
|
+
appendixCounter++;
|
|
514
|
+
lines.push(`- **Value**: [See Appendix ${label}]`);
|
|
515
|
+
appendixEntries.push({
|
|
516
|
+
key,
|
|
517
|
+
type: entryType,
|
|
518
|
+
contentType: attributes?.contentType || "application/octet-stream",
|
|
519
|
+
base64: value,
|
|
520
|
+
label
|
|
521
|
+
});
|
|
522
|
+
} else if (entryType === "json") {
|
|
523
|
+
lines.push("- **Value**:");
|
|
524
|
+
lines.push("```json");
|
|
525
|
+
try {
|
|
526
|
+
const jsonValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
527
|
+
lines.push(jsonValue);
|
|
528
|
+
} catch {
|
|
529
|
+
lines.push(String(value));
|
|
530
|
+
}
|
|
531
|
+
lines.push("```");
|
|
532
|
+
} else {
|
|
533
|
+
lines.push("- **Value**:");
|
|
534
|
+
lines.push("```");
|
|
535
|
+
lines.push(String(value));
|
|
536
|
+
lines.push("```");
|
|
537
|
+
}
|
|
538
|
+
lines.push("");
|
|
539
|
+
});
|
|
540
|
+
if (appendixEntries.length > 0) {
|
|
541
|
+
lines.push("---");
|
|
542
|
+
lines.push("");
|
|
543
|
+
lines.push("## Appendix: Binary Data");
|
|
544
|
+
lines.push("");
|
|
545
|
+
appendixEntries.forEach((entry) => {
|
|
546
|
+
lines.push(`### Appendix ${entry.label}: ${entry.key}`);
|
|
547
|
+
lines.push(`- **Type**: \`${entry.type}\``);
|
|
548
|
+
lines.push(`- **Content Type**: \`${entry.contentType}\``);
|
|
549
|
+
lines.push("- **Base64 Data**:");
|
|
550
|
+
lines.push("```");
|
|
551
|
+
lines.push(entry.base64);
|
|
552
|
+
lines.push("```");
|
|
553
|
+
lines.push("");
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
return lines.join("\n");
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Import Markdown into MindCache data.
|
|
560
|
+
* @param markdown The markdown string to import
|
|
561
|
+
* @param mc The MindCache instance to import into
|
|
562
|
+
* @param merge If false (default), clears existing data before importing
|
|
563
|
+
*/
|
|
564
|
+
static fromMarkdown(markdown, mc, merge = false) {
|
|
565
|
+
const lines = markdown.split("\n");
|
|
566
|
+
let currentKey = null;
|
|
567
|
+
let currentAttributes = {};
|
|
568
|
+
let currentValue = null;
|
|
569
|
+
let inCodeBlock = false;
|
|
570
|
+
let codeBlockContent = [];
|
|
571
|
+
if (!merge) {
|
|
572
|
+
mc.clear();
|
|
573
|
+
}
|
|
574
|
+
for (const line of lines) {
|
|
575
|
+
if (line.startsWith("### ") && !line.startsWith("### Appendix")) {
|
|
576
|
+
if (currentKey && currentValue !== null) {
|
|
577
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
578
|
+
}
|
|
579
|
+
currentKey = line.substring(4).trim();
|
|
580
|
+
currentAttributes = {};
|
|
581
|
+
currentValue = null;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (line.startsWith("### Appendix ")) {
|
|
585
|
+
if (currentKey && currentValue !== null) {
|
|
586
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
587
|
+
}
|
|
588
|
+
const match = line.match(/### Appendix ([A-Z]): (.+)/);
|
|
589
|
+
if (match) {
|
|
590
|
+
currentKey = match[2];
|
|
591
|
+
currentAttributes = {};
|
|
592
|
+
currentValue = null;
|
|
593
|
+
}
|
|
594
|
+
continue;
|
|
595
|
+
}
|
|
596
|
+
if (line.startsWith("- **Type**:")) {
|
|
597
|
+
const type = line.match(/`(.+)`/)?.[1];
|
|
598
|
+
if (type) {
|
|
599
|
+
currentAttributes.type = type;
|
|
600
|
+
}
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (line.startsWith("- **System Tags**:")) {
|
|
604
|
+
const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
|
|
605
|
+
if (tagsStr !== "none") {
|
|
606
|
+
currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
|
|
607
|
+
}
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
if (line.startsWith("- **Z-Index**:")) {
|
|
611
|
+
const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
|
|
612
|
+
currentAttributes.zIndex = zIndex;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (line.startsWith("- **Tags**:")) {
|
|
616
|
+
const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
|
|
617
|
+
currentAttributes.contentTags = tags;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (line.startsWith("- **Content Type**:")) {
|
|
621
|
+
currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
if (line.startsWith("- **Base64 Data**:")) {
|
|
625
|
+
currentValue = "";
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (line.startsWith("- **Value**:")) {
|
|
629
|
+
const afterValue = line.substring(12).trim();
|
|
630
|
+
if (afterValue.includes("[See Appendix")) {
|
|
631
|
+
if (currentKey) {
|
|
632
|
+
mc.set_value(currentKey, "", currentAttributes);
|
|
633
|
+
}
|
|
634
|
+
currentValue = null;
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
if (afterValue === "") {
|
|
638
|
+
currentValue = "";
|
|
639
|
+
} else if (afterValue === "```" || afterValue === "```json") {
|
|
640
|
+
inCodeBlock = true;
|
|
641
|
+
codeBlockContent = [];
|
|
642
|
+
currentValue = "";
|
|
643
|
+
} else if (afterValue.startsWith("```")) {
|
|
644
|
+
inCodeBlock = true;
|
|
645
|
+
codeBlockContent = [afterValue.substring(3)];
|
|
646
|
+
currentValue = "";
|
|
647
|
+
} else {
|
|
648
|
+
currentValue = afterValue;
|
|
649
|
+
}
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const trimmedLine = line.trim();
|
|
653
|
+
if (trimmedLine === "```json" || trimmedLine === "```") {
|
|
654
|
+
if (inCodeBlock) {
|
|
655
|
+
inCodeBlock = false;
|
|
656
|
+
if (currentKey && codeBlockContent.length > 0) {
|
|
657
|
+
currentValue = codeBlockContent.join("\n");
|
|
658
|
+
}
|
|
659
|
+
codeBlockContent = [];
|
|
660
|
+
} else {
|
|
661
|
+
inCodeBlock = true;
|
|
662
|
+
codeBlockContent = [];
|
|
663
|
+
}
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
if (inCodeBlock) {
|
|
667
|
+
codeBlockContent.push(line);
|
|
668
|
+
} else if (currentKey && currentValue !== null) {
|
|
669
|
+
if (line.trim() === "---" || line.startsWith("## Appendix")) {
|
|
670
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
671
|
+
currentKey = null;
|
|
672
|
+
currentValue = null;
|
|
673
|
+
currentAttributes = {};
|
|
674
|
+
} else {
|
|
675
|
+
currentValue += "\n" + line;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (currentKey && currentValue !== null) {
|
|
680
|
+
mc.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
681
|
+
}
|
|
682
|
+
const hasParsedKeys = lines.some((line) => line.startsWith("### ") && !line.startsWith("### Appendix"));
|
|
683
|
+
const isSTMExport = markdown.includes("# MindCache STM Export") || markdown.includes("## STM Entries");
|
|
684
|
+
if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
|
|
685
|
+
mc.set_value("imported_content", markdown.trim(), {
|
|
686
|
+
type: "text",
|
|
687
|
+
systemTags: ["SystemPrompt", "LLMWrite"],
|
|
688
|
+
// Default assumptions
|
|
689
|
+
zIndex: 0
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Parse markdown and return STM data without applying to a MindCache instance.
|
|
695
|
+
* Useful for validation or preview.
|
|
696
|
+
*/
|
|
697
|
+
static parseMarkdown(markdown) {
|
|
698
|
+
const result = {};
|
|
699
|
+
const lines = markdown.split("\n");
|
|
700
|
+
let currentKey = null;
|
|
701
|
+
let currentAttributes = {};
|
|
702
|
+
let currentValue = null;
|
|
703
|
+
let inCodeBlock = false;
|
|
704
|
+
let codeBlockContent = [];
|
|
705
|
+
const saveEntry = () => {
|
|
706
|
+
if (currentKey && currentValue !== null) {
|
|
707
|
+
result[currentKey] = {
|
|
708
|
+
value: currentValue.trim(),
|
|
709
|
+
attributes: { ...DEFAULT_KEY_ATTRIBUTES, ...currentAttributes }
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
for (const line of lines) {
|
|
714
|
+
if (line.startsWith("### ") && !line.startsWith("### Appendix")) {
|
|
715
|
+
saveEntry();
|
|
716
|
+
currentKey = line.substring(4).trim();
|
|
717
|
+
currentAttributes = {};
|
|
718
|
+
currentValue = null;
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (line.startsWith("### Appendix ")) {
|
|
722
|
+
saveEntry();
|
|
723
|
+
const match = line.match(/### Appendix ([A-Z]): (.+)/);
|
|
724
|
+
if (match) {
|
|
725
|
+
currentKey = match[2];
|
|
726
|
+
currentAttributes = {};
|
|
727
|
+
currentValue = null;
|
|
728
|
+
}
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (line.startsWith("- **Type**:")) {
|
|
732
|
+
const type = line.match(/`(.+)`/)?.[1];
|
|
733
|
+
if (type) {
|
|
734
|
+
currentAttributes.type = type;
|
|
735
|
+
}
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
if (line.startsWith("- **System Tags**:")) {
|
|
739
|
+
const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
|
|
740
|
+
if (tagsStr !== "none") {
|
|
741
|
+
currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
|
|
742
|
+
}
|
|
743
|
+
continue;
|
|
744
|
+
}
|
|
745
|
+
if (line.startsWith("- **Z-Index**:")) {
|
|
746
|
+
const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
|
|
747
|
+
currentAttributes.zIndex = zIndex;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
if (line.startsWith("- **Tags**:")) {
|
|
751
|
+
const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
|
|
752
|
+
currentAttributes.contentTags = tags;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (line.startsWith("- **Content Type**:")) {
|
|
756
|
+
currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (line.startsWith("- **Base64 Data**:")) {
|
|
760
|
+
currentValue = "";
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
if (line.startsWith("- **Value**:")) {
|
|
764
|
+
const afterValue = line.substring(12).trim();
|
|
765
|
+
if (afterValue.includes("[See Appendix")) {
|
|
766
|
+
currentValue = "";
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
if (afterValue === "" || afterValue === "```" || afterValue === "```json") {
|
|
770
|
+
inCodeBlock = afterValue !== "";
|
|
771
|
+
codeBlockContent = [];
|
|
772
|
+
currentValue = "";
|
|
773
|
+
} else if (afterValue.startsWith("```")) {
|
|
774
|
+
inCodeBlock = true;
|
|
775
|
+
codeBlockContent = [afterValue.substring(3)];
|
|
776
|
+
currentValue = "";
|
|
777
|
+
} else {
|
|
778
|
+
currentValue = afterValue;
|
|
779
|
+
}
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
const trimmedLine = line.trim();
|
|
783
|
+
if (trimmedLine === "```json" || trimmedLine === "```") {
|
|
784
|
+
if (inCodeBlock) {
|
|
785
|
+
inCodeBlock = false;
|
|
786
|
+
if (currentKey && codeBlockContent.length > 0) {
|
|
787
|
+
currentValue = codeBlockContent.join("\n");
|
|
788
|
+
}
|
|
789
|
+
codeBlockContent = [];
|
|
790
|
+
} else {
|
|
791
|
+
inCodeBlock = true;
|
|
792
|
+
codeBlockContent = [];
|
|
793
|
+
}
|
|
794
|
+
continue;
|
|
795
|
+
}
|
|
796
|
+
if (inCodeBlock) {
|
|
797
|
+
codeBlockContent.push(line);
|
|
798
|
+
} else if (currentKey && currentValue !== null) {
|
|
799
|
+
if (line.trim() === "---" || line.startsWith("## Appendix")) {
|
|
800
|
+
saveEntry();
|
|
801
|
+
currentKey = null;
|
|
802
|
+
currentValue = null;
|
|
803
|
+
currentAttributes = {};
|
|
804
|
+
} else {
|
|
805
|
+
currentValue += "\n" + line;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
saveEntry();
|
|
810
|
+
const hasParsedKeys = lines.some((line) => line.startsWith("### ") && !line.startsWith("### Appendix"));
|
|
811
|
+
const isSTMExport = markdown.includes("# MindCache STM Export") || markdown.includes("## STM Entries");
|
|
812
|
+
if (!hasParsedKeys && !isSTMExport && markdown.trim().length > 0) {
|
|
813
|
+
result["imported_content"] = {
|
|
814
|
+
value: markdown.trim(),
|
|
815
|
+
attributes: {
|
|
816
|
+
...DEFAULT_KEY_ATTRIBUTES,
|
|
817
|
+
systemTags: ["SystemPrompt", "LLMWrite"]
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
return result;
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
// src/core/AIToolBuilder.ts
|
|
826
|
+
var AIToolBuilder = class _AIToolBuilder {
|
|
827
|
+
/**
|
|
828
|
+
* Sanitize key name for use in tool names
|
|
829
|
+
*/
|
|
830
|
+
static sanitizeKeyForTool(key) {
|
|
831
|
+
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
832
|
+
}
|
|
833
|
+
/**
|
|
834
|
+
* Find original key from sanitized tool name
|
|
835
|
+
*/
|
|
836
|
+
static findKeyFromSanitizedTool(mc, sanitizedKey) {
|
|
837
|
+
for (const key of mc.keys()) {
|
|
838
|
+
if (_AIToolBuilder.sanitizeKeyForTool(key) === sanitizedKey) {
|
|
839
|
+
return key;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
846
|
+
* For document type keys, generates additional tools: append_, insert_, edit_
|
|
847
|
+
*
|
|
848
|
+
* Security: All tools use llm_set_key internally which:
|
|
849
|
+
* - Only modifies VALUES, never attributes/systemTags
|
|
850
|
+
* - Prevents LLMs from escalating privileges
|
|
851
|
+
*/
|
|
852
|
+
static createVercelAITools(mc) {
|
|
853
|
+
const tools = {};
|
|
854
|
+
for (const key of mc.keys()) {
|
|
855
|
+
if (key.startsWith("$")) {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (!mc.keyMatchesContext(key)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
const attributes = mc.get_attributes(key);
|
|
862
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
863
|
+
if (!isWritable) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
867
|
+
const isDocument = attributes?.type === "document";
|
|
868
|
+
tools[`write_${sanitizedKey}`] = {
|
|
869
|
+
description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: "object",
|
|
872
|
+
properties: {
|
|
873
|
+
value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
|
|
874
|
+
},
|
|
875
|
+
required: ["value"]
|
|
876
|
+
},
|
|
877
|
+
execute: async ({ value }) => {
|
|
878
|
+
const success = mc.llm_set_key(key, value);
|
|
879
|
+
if (success) {
|
|
880
|
+
return {
|
|
881
|
+
result: `Successfully wrote "${value}" to ${key}`,
|
|
882
|
+
key,
|
|
883
|
+
value
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
result: `Failed to write to ${key} - permission denied or key not found`,
|
|
888
|
+
key,
|
|
889
|
+
error: true
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
if (isDocument) {
|
|
894
|
+
tools[`append_${sanitizedKey}`] = {
|
|
895
|
+
description: `Append text to the end of "${key}" document`,
|
|
896
|
+
inputSchema: {
|
|
897
|
+
type: "object",
|
|
898
|
+
properties: {
|
|
899
|
+
text: { type: "string", description: "Text to append" }
|
|
900
|
+
},
|
|
901
|
+
required: ["text"]
|
|
902
|
+
},
|
|
903
|
+
execute: async ({ text }) => {
|
|
904
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
905
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
906
|
+
}
|
|
907
|
+
const yText = mc.get_document(key);
|
|
908
|
+
if (yText) {
|
|
909
|
+
yText.insert(yText.length, text);
|
|
910
|
+
return {
|
|
911
|
+
result: `Successfully appended to ${key}`,
|
|
912
|
+
key,
|
|
913
|
+
appended: text
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return { result: `Document ${key} not found`, key };
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
tools[`insert_${sanitizedKey}`] = {
|
|
920
|
+
description: `Insert text at a position in "${key}" document`,
|
|
921
|
+
inputSchema: {
|
|
922
|
+
type: "object",
|
|
923
|
+
properties: {
|
|
924
|
+
index: { type: "number", description: "Position to insert at (0 = start)" },
|
|
925
|
+
text: { type: "string", description: "Text to insert" }
|
|
926
|
+
},
|
|
927
|
+
required: ["index", "text"]
|
|
928
|
+
},
|
|
929
|
+
execute: async ({ index, text }) => {
|
|
930
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
931
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
932
|
+
}
|
|
933
|
+
mc.insert_text(key, index, text);
|
|
934
|
+
return {
|
|
935
|
+
result: `Successfully inserted text at position ${index} in ${key}`,
|
|
936
|
+
key,
|
|
937
|
+
index,
|
|
938
|
+
inserted: text
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
tools[`edit_${sanitizedKey}`] = {
|
|
943
|
+
description: `Find and replace text in "${key}" document`,
|
|
944
|
+
inputSchema: {
|
|
945
|
+
type: "object",
|
|
946
|
+
properties: {
|
|
947
|
+
find: { type: "string", description: "Text to find" },
|
|
948
|
+
replace: { type: "string", description: "Replacement text" }
|
|
949
|
+
},
|
|
950
|
+
required: ["find", "replace"]
|
|
951
|
+
},
|
|
952
|
+
execute: async ({ find, replace }) => {
|
|
953
|
+
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
954
|
+
return { result: `Permission denied for ${key}`, key, error: true };
|
|
955
|
+
}
|
|
956
|
+
const yText = mc.get_document(key);
|
|
957
|
+
if (yText) {
|
|
958
|
+
const text = yText.toString();
|
|
959
|
+
const idx = text.indexOf(find);
|
|
960
|
+
if (idx !== -1) {
|
|
961
|
+
yText.delete(idx, find.length);
|
|
962
|
+
yText.insert(idx, replace);
|
|
963
|
+
return {
|
|
964
|
+
result: `Successfully replaced "${find}" with "${replace}" in ${key}`,
|
|
965
|
+
key,
|
|
966
|
+
find,
|
|
967
|
+
replace,
|
|
968
|
+
index: idx
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
return { result: `Text "${find}" not found in ${key}`, key };
|
|
972
|
+
}
|
|
973
|
+
return { result: `Document ${key} not found`, key };
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
return tools;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Generate a system prompt containing all visible STM keys and their values.
|
|
982
|
+
* Indicates which tools can be used to modify writable keys.
|
|
983
|
+
*/
|
|
984
|
+
static getSystemPrompt(mc) {
|
|
985
|
+
const lines = [];
|
|
986
|
+
for (const key of mc.keys()) {
|
|
987
|
+
if (key.startsWith("$")) {
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
if (!mc.keyMatchesContext(key)) {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
const attributes = mc.get_attributes(key);
|
|
994
|
+
const isVisible = attributes?.systemTags?.includes("SystemPrompt") || attributes?.systemTags?.includes("LLMRead");
|
|
995
|
+
if (!isVisible) {
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
const value = mc.get_value(key);
|
|
999
|
+
const displayValue = typeof value === "object" ? JSON.stringify(value) : value;
|
|
1000
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
1001
|
+
const isDocument = attributes?.type === "document";
|
|
1002
|
+
const sanitizedKey = _AIToolBuilder.sanitizeKeyForTool(key);
|
|
1003
|
+
if (isWritable) {
|
|
1004
|
+
if (isDocument) {
|
|
1005
|
+
lines.push(
|
|
1006
|
+
`${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
|
|
1007
|
+
);
|
|
1008
|
+
} else {
|
|
1009
|
+
const oldValueHint = displayValue ? ` This tool DOES NOT append \u2014 start your response with the old value (${displayValue})` : "";
|
|
1010
|
+
lines.push(
|
|
1011
|
+
`${key}: ${displayValue}. You can rewrite "${key}" by using the write_${sanitizedKey} tool.${oldValueHint}`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
} else {
|
|
1015
|
+
lines.push(`${key}: ${displayValue}`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return lines.join("\n");
|
|
1019
|
+
}
|
|
1020
|
+
/**
|
|
1021
|
+
* Execute a tool call by name with the given value.
|
|
1022
|
+
* Returns the result or null if tool not found.
|
|
1023
|
+
*/
|
|
1024
|
+
static executeToolCall(mc, toolName, value) {
|
|
1025
|
+
const match = toolName.match(/^(write|append|insert|edit)_(.+)$/);
|
|
1026
|
+
if (!match) {
|
|
1027
|
+
return null;
|
|
1028
|
+
}
|
|
1029
|
+
const [, action, sanitizedKey] = match;
|
|
1030
|
+
const key = _AIToolBuilder.findKeyFromSanitizedTool(mc, sanitizedKey);
|
|
1031
|
+
if (!key) {
|
|
1032
|
+
return null;
|
|
1033
|
+
}
|
|
1034
|
+
const attributes = mc.get_attributes(key);
|
|
1035
|
+
if (!attributes) {
|
|
1036
|
+
return null;
|
|
1037
|
+
}
|
|
1038
|
+
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
1039
|
+
if (!isWritable) {
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
const isDocument = attributes?.type === "document";
|
|
1043
|
+
switch (action) {
|
|
1044
|
+
case "write":
|
|
1045
|
+
if (isDocument) {
|
|
1046
|
+
mc._replaceDocumentText(key, value);
|
|
1047
|
+
} else {
|
|
1048
|
+
mc.set_value(key, value);
|
|
1049
|
+
}
|
|
1050
|
+
return {
|
|
1051
|
+
result: `Successfully wrote "${value}" to ${key}`,
|
|
1052
|
+
key,
|
|
1053
|
+
value
|
|
1054
|
+
};
|
|
1055
|
+
case "append":
|
|
1056
|
+
if (isDocument) {
|
|
1057
|
+
const yText = mc.get_document(key);
|
|
1058
|
+
if (yText) {
|
|
1059
|
+
yText.insert(yText.length, value);
|
|
1060
|
+
return {
|
|
1061
|
+
result: `Successfully appended to ${key}`,
|
|
1062
|
+
key,
|
|
1063
|
+
value
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return null;
|
|
1068
|
+
case "insert":
|
|
1069
|
+
if (isDocument && typeof value === "object" && value.index !== void 0 && value.text) {
|
|
1070
|
+
mc.insert_text(key, value.index, value.text);
|
|
1071
|
+
return {
|
|
1072
|
+
result: `Successfully inserted at position ${value.index} in ${key}`,
|
|
1073
|
+
key,
|
|
1074
|
+
value: value.text
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1077
|
+
return null;
|
|
1078
|
+
case "edit":
|
|
1079
|
+
if (isDocument && typeof value === "object" && value.find && value.replace !== void 0) {
|
|
1080
|
+
const yText = mc.get_document(key);
|
|
1081
|
+
if (yText) {
|
|
1082
|
+
const text = yText.toString();
|
|
1083
|
+
const idx = text.indexOf(value.find);
|
|
1084
|
+
if (idx !== -1) {
|
|
1085
|
+
yText.delete(idx, value.find.length);
|
|
1086
|
+
yText.insert(idx, value.replace);
|
|
1087
|
+
return {
|
|
1088
|
+
result: `Successfully replaced "${value.find}" with "${value.replace}" in ${key}`,
|
|
1089
|
+
key,
|
|
1090
|
+
value: value.replace
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return null;
|
|
1096
|
+
default:
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// src/core/TagManager.ts
|
|
1103
|
+
var TagManager = class _TagManager {
|
|
1104
|
+
// ============================================
|
|
1105
|
+
// Content Tag Methods
|
|
1106
|
+
// ============================================
|
|
1107
|
+
/**
|
|
1108
|
+
* Add a content tag to a key.
|
|
1109
|
+
* @returns true if the tag was added, false if key doesn't exist or tag already exists
|
|
1110
|
+
*/
|
|
1111
|
+
static addTag(mc, key, tag) {
|
|
1112
|
+
const entryMap = mc.rootMap.get(key);
|
|
1113
|
+
if (!entryMap) {
|
|
1114
|
+
return false;
|
|
1115
|
+
}
|
|
1116
|
+
const attributes = entryMap.get("attributes");
|
|
1117
|
+
const contentTags = attributes?.contentTags || [];
|
|
1118
|
+
if (contentTags.includes(tag)) {
|
|
1119
|
+
return false;
|
|
1120
|
+
}
|
|
1121
|
+
mc.doc.transact(() => {
|
|
1122
|
+
const newContentTags = [...contentTags, tag];
|
|
1123
|
+
entryMap.set("attributes", {
|
|
1124
|
+
...attributes,
|
|
1125
|
+
contentTags: newContentTags,
|
|
1126
|
+
tags: newContentTags
|
|
1127
|
+
// Sync legacy tags array
|
|
1128
|
+
});
|
|
1129
|
+
});
|
|
1130
|
+
mc.notifyGlobalListeners();
|
|
1131
|
+
return true;
|
|
1132
|
+
}
|
|
1133
|
+
/**
|
|
1134
|
+
* Remove a content tag from a key.
|
|
1135
|
+
* @returns true if the tag was removed
|
|
1136
|
+
*/
|
|
1137
|
+
static removeTag(mc, key, tag) {
|
|
1138
|
+
const entryMap = mc.rootMap.get(key);
|
|
1139
|
+
if (!entryMap) {
|
|
1140
|
+
return false;
|
|
1141
|
+
}
|
|
1142
|
+
const attributes = entryMap.get("attributes");
|
|
1143
|
+
const contentTags = attributes?.contentTags || [];
|
|
1144
|
+
const tagIndex = contentTags.indexOf(tag);
|
|
1145
|
+
if (tagIndex === -1) {
|
|
1146
|
+
return false;
|
|
1147
|
+
}
|
|
1148
|
+
mc.doc.transact(() => {
|
|
1149
|
+
const newContentTags = contentTags.filter((t) => t !== tag);
|
|
1150
|
+
entryMap.set("attributes", {
|
|
1151
|
+
...attributes,
|
|
1152
|
+
contentTags: newContentTags,
|
|
1153
|
+
tags: newContentTags
|
|
1154
|
+
// Sync legacy tags array
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
mc.notifyGlobalListeners();
|
|
1158
|
+
return true;
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Get all content tags for a key.
|
|
1162
|
+
*/
|
|
1163
|
+
static getTags(mc, key) {
|
|
1164
|
+
const entryMap = mc.rootMap.get(key);
|
|
1165
|
+
if (!entryMap) {
|
|
1166
|
+
return [];
|
|
1167
|
+
}
|
|
1168
|
+
const attributes = entryMap.get("attributes");
|
|
1169
|
+
return attributes?.contentTags || [];
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Get all unique content tags across all keys.
|
|
1173
|
+
*/
|
|
1174
|
+
static getAllTags(mc) {
|
|
1175
|
+
const allTags = /* @__PURE__ */ new Set();
|
|
1176
|
+
for (const [, val] of mc.rootMap) {
|
|
1177
|
+
const entryMap = val;
|
|
1178
|
+
const attributes = entryMap.get("attributes");
|
|
1179
|
+
if (attributes?.contentTags) {
|
|
1180
|
+
attributes.contentTags.forEach((tag) => allTags.add(tag));
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return Array.from(allTags);
|
|
1184
|
+
}
|
|
1185
|
+
/**
|
|
1186
|
+
* Check if a key has a specific content tag.
|
|
1187
|
+
*/
|
|
1188
|
+
static hasTag(mc, key, tag) {
|
|
1189
|
+
const entryMap = mc.rootMap.get(key);
|
|
1190
|
+
if (!entryMap) {
|
|
1191
|
+
return false;
|
|
1192
|
+
}
|
|
1193
|
+
const attributes = entryMap.get("attributes");
|
|
1194
|
+
return attributes?.contentTags?.includes(tag) || false;
|
|
1195
|
+
}
|
|
1196
|
+
/**
|
|
1197
|
+
* Get all keys with a specific content tag as formatted string.
|
|
1198
|
+
*/
|
|
1199
|
+
static getTagged(mc, tag) {
|
|
1200
|
+
const entries = [];
|
|
1201
|
+
const keys = mc.getSortedKeys();
|
|
1202
|
+
keys.forEach((key) => {
|
|
1203
|
+
if (_TagManager.hasTag(mc, key, tag)) {
|
|
1204
|
+
entries.push([key, mc.get_value(key)]);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Get array of keys with a specific content tag.
|
|
1211
|
+
*/
|
|
1212
|
+
static getKeysByTag(mc, tag) {
|
|
1213
|
+
const keys = mc.getSortedKeys();
|
|
1214
|
+
return keys.filter((key) => _TagManager.hasTag(mc, key, tag));
|
|
1215
|
+
}
|
|
1216
|
+
// ============================================
|
|
1217
|
+
// System Tag Methods (requires system access)
|
|
1218
|
+
// ============================================
|
|
1219
|
+
/**
|
|
1220
|
+
* Add a system tag to a key (requires system access).
|
|
1221
|
+
*/
|
|
1222
|
+
static systemAddTag(mc, key, tag) {
|
|
1223
|
+
if (!mc.hasSystemAccess) {
|
|
1224
|
+
console.warn("MindCache: systemAddTag requires system access level");
|
|
1225
|
+
return false;
|
|
1226
|
+
}
|
|
1227
|
+
const entryMap = mc.rootMap.get(key);
|
|
1228
|
+
if (!entryMap) {
|
|
1229
|
+
return false;
|
|
1230
|
+
}
|
|
1231
|
+
const attributes = entryMap.get("attributes");
|
|
1232
|
+
const systemTags = attributes?.systemTags || [];
|
|
1233
|
+
if (systemTags.includes(tag)) {
|
|
1234
|
+
return false;
|
|
1235
|
+
}
|
|
1236
|
+
mc.doc.transact(() => {
|
|
1237
|
+
const newSystemTags = [...systemTags, tag];
|
|
1238
|
+
const normalizedTags = mc.normalizeSystemTags(newSystemTags);
|
|
1239
|
+
entryMap.set("attributes", {
|
|
1240
|
+
...attributes,
|
|
1241
|
+
systemTags: normalizedTags
|
|
1242
|
+
});
|
|
1243
|
+
});
|
|
1244
|
+
mc.notifyGlobalListeners();
|
|
1245
|
+
return true;
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Remove a system tag from a key (requires system access).
|
|
1249
|
+
*/
|
|
1250
|
+
static systemRemoveTag(mc, key, tag) {
|
|
1251
|
+
if (!mc.hasSystemAccess) {
|
|
1252
|
+
console.warn("MindCache: systemRemoveTag requires system access level");
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
const entryMap = mc.rootMap.get(key);
|
|
1256
|
+
if (!entryMap) {
|
|
1257
|
+
return false;
|
|
1258
|
+
}
|
|
1259
|
+
const attributes = entryMap.get("attributes");
|
|
1260
|
+
const systemTags = attributes?.systemTags || [];
|
|
1261
|
+
const tagIndex = systemTags.indexOf(tag);
|
|
1262
|
+
if (tagIndex === -1) {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
mc.doc.transact(() => {
|
|
1266
|
+
const newSystemTags = systemTags.filter((t) => t !== tag);
|
|
1267
|
+
entryMap.set("attributes", {
|
|
1268
|
+
...attributes,
|
|
1269
|
+
systemTags: newSystemTags
|
|
1270
|
+
});
|
|
1271
|
+
});
|
|
1272
|
+
mc.notifyGlobalListeners();
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Get all system tags for a key (requires system access).
|
|
1277
|
+
*/
|
|
1278
|
+
static systemGetTags(mc, key) {
|
|
1279
|
+
if (!mc.hasSystemAccess) {
|
|
1280
|
+
console.warn("MindCache: systemGetTags requires system access level");
|
|
1281
|
+
return [];
|
|
1282
|
+
}
|
|
1283
|
+
const entryMap = mc.rootMap.get(key);
|
|
1284
|
+
if (!entryMap) {
|
|
1285
|
+
return [];
|
|
1286
|
+
}
|
|
1287
|
+
const attributes = entryMap.get("attributes");
|
|
1288
|
+
return attributes?.systemTags || [];
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Check if a key has a specific system tag (requires system access).
|
|
1292
|
+
*/
|
|
1293
|
+
static systemHasTag(mc, key, tag) {
|
|
1294
|
+
if (!mc.hasSystemAccess) {
|
|
1295
|
+
console.warn("MindCache: systemHasTag requires system access level");
|
|
1296
|
+
return false;
|
|
1297
|
+
}
|
|
1298
|
+
const entryMap = mc.rootMap.get(key);
|
|
1299
|
+
if (!entryMap) {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
const attributes = entryMap.get("attributes");
|
|
1303
|
+
return attributes?.systemTags?.includes(tag) || false;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Set all system tags for a key at once (requires system access).
|
|
1307
|
+
*/
|
|
1308
|
+
static systemSetTags(mc, key, tags) {
|
|
1309
|
+
if (!mc.hasSystemAccess) {
|
|
1310
|
+
console.warn("MindCache: systemSetTags requires system access level");
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
const entryMap = mc.rootMap.get(key);
|
|
1314
|
+
if (!entryMap) {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
mc.doc.transact(() => {
|
|
1318
|
+
const attributes = entryMap.get("attributes");
|
|
1319
|
+
entryMap.set("attributes", {
|
|
1320
|
+
...attributes,
|
|
1321
|
+
systemTags: [...tags]
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
mc.notifyGlobalListeners();
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Get all keys with a specific system tag (requires system access).
|
|
1329
|
+
*/
|
|
1330
|
+
static systemGetKeysByTag(mc, tag) {
|
|
1331
|
+
if (!mc.hasSystemAccess) {
|
|
1332
|
+
console.warn("MindCache: systemGetKeysByTag requires system access level");
|
|
1333
|
+
return [];
|
|
1334
|
+
}
|
|
1335
|
+
const keys = mc.getSortedKeys();
|
|
1336
|
+
return keys.filter((key) => _TagManager.systemHasTag(mc, key, tag));
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
|
|
480
1340
|
// src/core/MindCache.ts
|
|
481
1341
|
var MindCache = class {
|
|
482
1342
|
// Public doc for adapter access
|
|
@@ -494,7 +1354,7 @@ var MindCache = class {
|
|
|
494
1354
|
const normalized = [];
|
|
495
1355
|
const seen = /* @__PURE__ */ new Set();
|
|
496
1356
|
for (const tag of tags) {
|
|
497
|
-
if (["SystemPrompt", "LLMRead", "LLMWrite", "
|
|
1357
|
+
if (["SystemPrompt", "LLMRead", "LLMWrite", "ApplyTemplate"].includes(tag)) {
|
|
498
1358
|
if (!seen.has(tag)) {
|
|
499
1359
|
seen.add(tag);
|
|
500
1360
|
normalized.push(tag);
|
|
@@ -509,8 +1369,10 @@ var MindCache = class {
|
|
|
509
1369
|
_isLoaded = true;
|
|
510
1370
|
// Default true for local mode
|
|
511
1371
|
_cloudConfig = null;
|
|
512
|
-
// Access level for
|
|
1372
|
+
// Access level for admin operations
|
|
513
1373
|
_accessLevel = "user";
|
|
1374
|
+
// Context filtering (client-local, not persisted)
|
|
1375
|
+
_contextRules = null;
|
|
514
1376
|
_initPromise = null;
|
|
515
1377
|
// Y-IndexedDB provider
|
|
516
1378
|
_idbProvider = null;
|
|
@@ -575,6 +1437,9 @@ var MindCache = class {
|
|
|
575
1437
|
if (options?.accessLevel) {
|
|
576
1438
|
this._accessLevel = options.accessLevel;
|
|
577
1439
|
}
|
|
1440
|
+
if (options?.context) {
|
|
1441
|
+
this._contextRules = options.context;
|
|
1442
|
+
}
|
|
578
1443
|
const initPromises = [];
|
|
579
1444
|
if (options?.cloud) {
|
|
580
1445
|
this._cloudConfig = options.cloud;
|
|
@@ -749,7 +1614,101 @@ var MindCache = class {
|
|
|
749
1614
|
return this._accessLevel;
|
|
750
1615
|
}
|
|
751
1616
|
get hasSystemAccess() {
|
|
752
|
-
return this._accessLevel === "
|
|
1617
|
+
return this._accessLevel === "admin";
|
|
1618
|
+
}
|
|
1619
|
+
// ============================================
|
|
1620
|
+
// Context Methods (client-local filtering)
|
|
1621
|
+
// ============================================
|
|
1622
|
+
/**
|
|
1623
|
+
* Check if context filtering is currently active.
|
|
1624
|
+
*/
|
|
1625
|
+
get hasContext() {
|
|
1626
|
+
return this._contextRules !== null;
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Get current context rules, or null if no context is set.
|
|
1630
|
+
*/
|
|
1631
|
+
get_context() {
|
|
1632
|
+
return this._contextRules;
|
|
1633
|
+
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Set context filtering rules.
|
|
1636
|
+
* When context is set, only keys with ALL specified tags are visible.
|
|
1637
|
+
*
|
|
1638
|
+
* @param rules - Context rules, or array of tags (shorthand for { tags: [...] })
|
|
1639
|
+
*/
|
|
1640
|
+
set_context(rules) {
|
|
1641
|
+
if (Array.isArray(rules)) {
|
|
1642
|
+
this._contextRules = { tags: rules };
|
|
1643
|
+
} else {
|
|
1644
|
+
this._contextRules = rules;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
/**
|
|
1648
|
+
* Clear context filtering. All keys become visible again.
|
|
1649
|
+
*/
|
|
1650
|
+
reset_context() {
|
|
1651
|
+
this._contextRules = null;
|
|
1652
|
+
}
|
|
1653
|
+
/**
|
|
1654
|
+
* Check if a key matches the current context rules.
|
|
1655
|
+
* Returns true if no context is set.
|
|
1656
|
+
*/
|
|
1657
|
+
keyMatchesContext(key) {
|
|
1658
|
+
if (key.startsWith("$")) {
|
|
1659
|
+
return true;
|
|
1660
|
+
}
|
|
1661
|
+
if (!this._contextRules) {
|
|
1662
|
+
return true;
|
|
1663
|
+
}
|
|
1664
|
+
if (this._contextRules.tags.length === 0) {
|
|
1665
|
+
return true;
|
|
1666
|
+
}
|
|
1667
|
+
const entryMap = this.rootMap.get(key);
|
|
1668
|
+
if (!entryMap) {
|
|
1669
|
+
return false;
|
|
1670
|
+
}
|
|
1671
|
+
const attrs = entryMap.get("attributes");
|
|
1672
|
+
const contentTags = attrs?.contentTags || [];
|
|
1673
|
+
return this._contextRules.tags.every((tag) => contentTags.includes(tag));
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* Create a new key with optional default tags from context.
|
|
1677
|
+
*
|
|
1678
|
+
* @throws Error if key already exists
|
|
1679
|
+
*/
|
|
1680
|
+
create_key(key, value, attributes) {
|
|
1681
|
+
if (this.rootMap.has(key)) {
|
|
1682
|
+
throw new Error(`Key already exists: ${key}. Use set_value to update.`);
|
|
1683
|
+
}
|
|
1684
|
+
let finalAttributes = { ...attributes };
|
|
1685
|
+
if (this._contextRules) {
|
|
1686
|
+
const contextContentTags = this._contextRules.defaultContentTags || this._contextRules.tags;
|
|
1687
|
+
const existingContentTags = finalAttributes.contentTags || [];
|
|
1688
|
+
finalAttributes.contentTags = [.../* @__PURE__ */ new Set([...existingContentTags, ...contextContentTags])];
|
|
1689
|
+
if (this._contextRules.defaultSystemTags) {
|
|
1690
|
+
const existingSystemTags = finalAttributes.systemTags || [];
|
|
1691
|
+
finalAttributes.systemTags = [.../* @__PURE__ */ new Set([...existingSystemTags, ...this._contextRules.defaultSystemTags])];
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
const entryMap = new Y__namespace.Map();
|
|
1695
|
+
this.rootMap.set(key, entryMap);
|
|
1696
|
+
this.getUndoManager(key);
|
|
1697
|
+
this.doc.transact(() => {
|
|
1698
|
+
const baseAttributes = {
|
|
1699
|
+
...DEFAULT_KEY_ATTRIBUTES,
|
|
1700
|
+
...finalAttributes
|
|
1701
|
+
};
|
|
1702
|
+
if (baseAttributes.systemTags) {
|
|
1703
|
+
baseAttributes.systemTags = this.normalizeSystemTags(baseAttributes.systemTags);
|
|
1704
|
+
}
|
|
1705
|
+
let valueToSet = value;
|
|
1706
|
+
if (baseAttributes.type === "document" && !(valueToSet instanceof Y__namespace.Text)) {
|
|
1707
|
+
valueToSet = new Y__namespace.Text(typeof value === "string" ? value : String(value ?? ""));
|
|
1708
|
+
}
|
|
1709
|
+
entryMap.set("value", valueToSet);
|
|
1710
|
+
entryMap.set("attributes", baseAttributes);
|
|
1711
|
+
});
|
|
753
1712
|
}
|
|
754
1713
|
async _initCloud() {
|
|
755
1714
|
if (!this._cloudConfig) {
|
|
@@ -924,10 +1883,11 @@ var MindCache = class {
|
|
|
924
1883
|
this.rootMap.set(key, entryMap);
|
|
925
1884
|
entryMap.set("value", entry.value);
|
|
926
1885
|
const attrs = entry.attributes || {};
|
|
1886
|
+
const contentTags = attrs.contentTags || attrs.tags || [];
|
|
927
1887
|
const normalizedAttrs = {
|
|
928
1888
|
type: attrs.type || "text",
|
|
929
1889
|
contentType: attrs.contentType,
|
|
930
|
-
contentTags
|
|
1890
|
+
contentTags,
|
|
931
1891
|
systemTags: this.normalizeSystemTags(attrs.systemTags || []),
|
|
932
1892
|
zIndex: attrs.zIndex ?? 0
|
|
933
1893
|
};
|
|
@@ -970,10 +1930,21 @@ var MindCache = class {
|
|
|
970
1930
|
return false;
|
|
971
1931
|
}
|
|
972
1932
|
// InjectSTM replacement (private helper)
|
|
1933
|
+
// Handles special template variables: $date, $time, $version
|
|
973
1934
|
_injectSTMInternal(template, _processingStack) {
|
|
974
1935
|
return template.replace(/\{\{([^}]+)\}\}/g, (_, key) => {
|
|
975
|
-
const
|
|
976
|
-
|
|
1936
|
+
const trimmedKey = key.trim();
|
|
1937
|
+
if (trimmedKey === "$date") {
|
|
1938
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1939
|
+
}
|
|
1940
|
+
if (trimmedKey === "$time") {
|
|
1941
|
+
return (/* @__PURE__ */ new Date()).toTimeString().split(" ")[0];
|
|
1942
|
+
}
|
|
1943
|
+
if (trimmedKey === "$version") {
|
|
1944
|
+
return this.version;
|
|
1945
|
+
}
|
|
1946
|
+
const val = this.get_value(trimmedKey, _processingStack);
|
|
1947
|
+
return val !== void 0 ? String(val) : "";
|
|
977
1948
|
});
|
|
978
1949
|
}
|
|
979
1950
|
/**
|
|
@@ -988,10 +1959,10 @@ var MindCache = class {
|
|
|
988
1959
|
getAll() {
|
|
989
1960
|
const result = {};
|
|
990
1961
|
for (const [key] of this.rootMap) {
|
|
991
|
-
|
|
1962
|
+
if (this.keyMatchesContext(key)) {
|
|
1963
|
+
result[key] = this.get_value(key);
|
|
1964
|
+
}
|
|
992
1965
|
}
|
|
993
|
-
result["$date"] = this.get_value("$date");
|
|
994
|
-
result["$time"] = this.get_value("$time");
|
|
995
1966
|
return result;
|
|
996
1967
|
}
|
|
997
1968
|
/**
|
|
@@ -1002,30 +1973,24 @@ var MindCache = class {
|
|
|
1002
1973
|
getAllEntries() {
|
|
1003
1974
|
const result = {};
|
|
1004
1975
|
for (const [key] of this.rootMap) {
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1976
|
+
if (this.keyMatchesContext(key)) {
|
|
1977
|
+
const value = this.get_value(key);
|
|
1978
|
+
const attributes = this.get_attributes(key);
|
|
1979
|
+
if (attributes) {
|
|
1980
|
+
result[key] = { value, attributes };
|
|
1981
|
+
}
|
|
1009
1982
|
}
|
|
1010
1983
|
}
|
|
1011
1984
|
return result;
|
|
1012
1985
|
}
|
|
1013
1986
|
get_value(key, _processingStack) {
|
|
1014
|
-
if (key === "$date") {
|
|
1015
|
-
const today = /* @__PURE__ */ new Date();
|
|
1016
|
-
return today.toISOString().split("T")[0];
|
|
1017
|
-
}
|
|
1018
|
-
if (key === "$time") {
|
|
1019
|
-
const now = /* @__PURE__ */ new Date();
|
|
1020
|
-
return now.toTimeString().split(" ")[0];
|
|
1021
|
-
}
|
|
1022
|
-
if (key === "$version") {
|
|
1023
|
-
return this.version;
|
|
1024
|
-
}
|
|
1025
1987
|
const entryMap = this.rootMap.get(key);
|
|
1026
1988
|
if (!entryMap) {
|
|
1027
1989
|
return void 0;
|
|
1028
1990
|
}
|
|
1991
|
+
if (!this.keyMatchesContext(key)) {
|
|
1992
|
+
return void 0;
|
|
1993
|
+
}
|
|
1029
1994
|
const attributes = entryMap.get("attributes");
|
|
1030
1995
|
const value = entryMap.get("value");
|
|
1031
1996
|
if (attributes?.type === "document" && value instanceof Y__namespace.Text) {
|
|
@@ -1044,28 +2009,21 @@ var MindCache = class {
|
|
|
1044
2009
|
return value;
|
|
1045
2010
|
}
|
|
1046
2011
|
get_attributes(key) {
|
|
1047
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1048
|
-
return {
|
|
1049
|
-
type: "text",
|
|
1050
|
-
contentTags: [],
|
|
1051
|
-
systemTags: ["SystemPrompt", "protected"],
|
|
1052
|
-
zIndex: 999999
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
2012
|
const entryMap = this.rootMap.get(key);
|
|
1056
2013
|
return entryMap ? entryMap.get("attributes") : void 0;
|
|
1057
2014
|
}
|
|
1058
2015
|
/**
|
|
1059
2016
|
* Update only the attributes of a key without modifying the value.
|
|
1060
2017
|
* Useful for updating tags, permissions etc. on document type keys.
|
|
2018
|
+
* @returns true if attributes were updated, false if key doesn't exist or is protected
|
|
1061
2019
|
*/
|
|
1062
2020
|
set_attributes(key, attributes) {
|
|
1063
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1064
|
-
return;
|
|
1065
|
-
}
|
|
1066
2021
|
const entryMap = this.rootMap.get(key);
|
|
1067
2022
|
if (!entryMap) {
|
|
1068
|
-
return;
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
if (this._contextRules && !this.keyMatchesContext(key)) {
|
|
2026
|
+
throw new Error(`Cannot modify key "${key}": does not match current context`);
|
|
1069
2027
|
}
|
|
1070
2028
|
this.doc.transact(() => {
|
|
1071
2029
|
const existingAttrs = entryMap.get("attributes");
|
|
@@ -1083,13 +2041,14 @@ var MindCache = class {
|
|
|
1083
2041
|
entryMap.set("value", currentValue.toString());
|
|
1084
2042
|
}
|
|
1085
2043
|
});
|
|
2044
|
+
return true;
|
|
1086
2045
|
}
|
|
1087
2046
|
set_value(key, value, attributes) {
|
|
1088
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
2047
|
const existingEntry = this.rootMap.get(key);
|
|
1092
2048
|
if (existingEntry) {
|
|
2049
|
+
if (this._contextRules && !this.keyMatchesContext(key)) {
|
|
2050
|
+
throw new Error(`Cannot modify key "${key}": does not match current context`);
|
|
2051
|
+
}
|
|
1093
2052
|
const existingAttrs = existingEntry.get("attributes");
|
|
1094
2053
|
if (existingAttrs?.type === "document") {
|
|
1095
2054
|
if (!attributes?.type || attributes.type === "document") {
|
|
@@ -1103,6 +2062,10 @@ var MindCache = class {
|
|
|
1103
2062
|
}
|
|
1104
2063
|
}
|
|
1105
2064
|
}
|
|
2065
|
+
if (!existingEntry && this._contextRules) {
|
|
2066
|
+
this.create_key(key, value, attributes);
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
1106
2069
|
if (!existingEntry && attributes?.type === "document") {
|
|
1107
2070
|
this.set_document(key, typeof value === "string" ? value : "", attributes);
|
|
1108
2071
|
return;
|
|
@@ -1143,9 +2106,6 @@ var MindCache = class {
|
|
|
1143
2106
|
* Used by create_vercel_ai_tools() to prevent LLMs from escalating privileges.
|
|
1144
2107
|
*/
|
|
1145
2108
|
llm_set_key(key, value) {
|
|
1146
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1147
|
-
return false;
|
|
1148
|
-
}
|
|
1149
2109
|
const entryMap = this.rootMap.get(key);
|
|
1150
2110
|
if (!entryMap) {
|
|
1151
2111
|
return false;
|
|
@@ -1166,9 +2126,6 @@ var MindCache = class {
|
|
|
1166
2126
|
return true;
|
|
1167
2127
|
}
|
|
1168
2128
|
delete_key(key) {
|
|
1169
|
-
if (key === "$date" || key === "$time") {
|
|
1170
|
-
return;
|
|
1171
|
-
}
|
|
1172
2129
|
this.rootMap.delete(key);
|
|
1173
2130
|
}
|
|
1174
2131
|
clear() {
|
|
@@ -1184,19 +2141,16 @@ var MindCache = class {
|
|
|
1184
2141
|
* Check if a key exists in MindCache.
|
|
1185
2142
|
*/
|
|
1186
2143
|
has(key) {
|
|
1187
|
-
if (key
|
|
1188
|
-
return
|
|
2144
|
+
if (!this.rootMap.has(key)) {
|
|
2145
|
+
return false;
|
|
1189
2146
|
}
|
|
1190
|
-
return this.
|
|
2147
|
+
return this.keyMatchesContext(key);
|
|
1191
2148
|
}
|
|
1192
2149
|
/**
|
|
1193
2150
|
* Delete a key from MindCache.
|
|
1194
2151
|
* @returns true if the key existed and was deleted
|
|
1195
2152
|
*/
|
|
1196
2153
|
delete(key) {
|
|
1197
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1198
|
-
return false;
|
|
1199
|
-
}
|
|
1200
2154
|
if (!this.rootMap.has(key)) {
|
|
1201
2155
|
return false;
|
|
1202
2156
|
}
|
|
@@ -1222,9 +2176,7 @@ var MindCache = class {
|
|
|
1222
2176
|
update(data) {
|
|
1223
2177
|
this.doc.transact(() => {
|
|
1224
2178
|
for (const [key, value] of Object.entries(data)) {
|
|
1225
|
-
|
|
1226
|
-
this.set_value(key, value);
|
|
1227
|
-
}
|
|
2179
|
+
this.set_value(key, value);
|
|
1228
2180
|
}
|
|
1229
2181
|
});
|
|
1230
2182
|
this.notifyGlobalListeners();
|
|
@@ -1233,38 +2185,48 @@ var MindCache = class {
|
|
|
1233
2185
|
* Get the number of keys in MindCache.
|
|
1234
2186
|
*/
|
|
1235
2187
|
size() {
|
|
1236
|
-
|
|
2188
|
+
let count = 0;
|
|
2189
|
+
for (const [key] of this.rootMap) {
|
|
2190
|
+
if (this.keyMatchesContext(key)) {
|
|
2191
|
+
count++;
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
return count;
|
|
1237
2195
|
}
|
|
1238
2196
|
/**
|
|
1239
|
-
* Get all keys in MindCache
|
|
2197
|
+
* Get all keys in MindCache.
|
|
1240
2198
|
*/
|
|
1241
2199
|
keys() {
|
|
1242
|
-
const keys =
|
|
1243
|
-
|
|
2200
|
+
const keys = [];
|
|
2201
|
+
for (const [key] of this.rootMap) {
|
|
2202
|
+
if (this.keyMatchesContext(key)) {
|
|
2203
|
+
keys.push(key);
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
1244
2206
|
return keys;
|
|
1245
2207
|
}
|
|
1246
2208
|
/**
|
|
1247
|
-
* Get all values in MindCache
|
|
2209
|
+
* Get all values in MindCache.
|
|
1248
2210
|
*/
|
|
1249
2211
|
values() {
|
|
1250
2212
|
const result = [];
|
|
1251
2213
|
for (const [key] of this.rootMap) {
|
|
1252
|
-
|
|
2214
|
+
if (this.keyMatchesContext(key)) {
|
|
2215
|
+
result.push(this.get_value(key));
|
|
2216
|
+
}
|
|
1253
2217
|
}
|
|
1254
|
-
result.push(this.get_value("$date"));
|
|
1255
|
-
result.push(this.get_value("$time"));
|
|
1256
2218
|
return result;
|
|
1257
2219
|
}
|
|
1258
2220
|
/**
|
|
1259
|
-
* Get all key-value entries
|
|
2221
|
+
* Get all key-value entries.
|
|
1260
2222
|
*/
|
|
1261
2223
|
entries() {
|
|
1262
2224
|
const result = [];
|
|
1263
2225
|
for (const [key] of this.rootMap) {
|
|
1264
|
-
|
|
2226
|
+
if (this.keyMatchesContext(key)) {
|
|
2227
|
+
result.push([key, this.get_value(key)]);
|
|
2228
|
+
}
|
|
1265
2229
|
}
|
|
1266
|
-
result.push(["$date", this.get_value("$date")]);
|
|
1267
|
-
result.push(["$time", this.get_value("$time")]);
|
|
1268
2230
|
return result;
|
|
1269
2231
|
}
|
|
1270
2232
|
/**
|
|
@@ -1285,7 +2247,6 @@ var MindCache = class {
|
|
|
1285
2247
|
}
|
|
1286
2248
|
/**
|
|
1287
2249
|
* Get the STM as an object with values directly (no attributes).
|
|
1288
|
-
* Includes system keys ($date, $time).
|
|
1289
2250
|
* @deprecated Use getAll() for full STM format
|
|
1290
2251
|
*/
|
|
1291
2252
|
getSTMObject() {
|
|
@@ -1293,8 +2254,6 @@ var MindCache = class {
|
|
|
1293
2254
|
for (const [key] of this.rootMap) {
|
|
1294
2255
|
result[key] = this.get_value(key);
|
|
1295
2256
|
}
|
|
1296
|
-
result["$date"] = this.get_value("$date");
|
|
1297
|
-
result["$time"] = this.get_value("$time");
|
|
1298
2257
|
return result;
|
|
1299
2258
|
}
|
|
1300
2259
|
/**
|
|
@@ -1302,479 +2261,132 @@ var MindCache = class {
|
|
|
1302
2261
|
* @returns true if the tag was added, false if key doesn't exist or tag already exists
|
|
1303
2262
|
*/
|
|
1304
2263
|
addTag(key, tag) {
|
|
1305
|
-
|
|
1306
|
-
return false;
|
|
1307
|
-
}
|
|
1308
|
-
const entryMap = this.rootMap.get(key);
|
|
1309
|
-
if (!entryMap) {
|
|
1310
|
-
return false;
|
|
1311
|
-
}
|
|
1312
|
-
const attributes = entryMap.get("attributes");
|
|
1313
|
-
const contentTags = attributes?.contentTags || [];
|
|
1314
|
-
if (contentTags.includes(tag)) {
|
|
1315
|
-
return false;
|
|
1316
|
-
}
|
|
1317
|
-
this.doc.transact(() => {
|
|
1318
|
-
const newContentTags = [...contentTags, tag];
|
|
1319
|
-
entryMap.set("attributes", {
|
|
1320
|
-
...attributes,
|
|
1321
|
-
contentTags: newContentTags,
|
|
1322
|
-
tags: newContentTags
|
|
1323
|
-
// Sync legacy tags array
|
|
1324
|
-
});
|
|
1325
|
-
});
|
|
1326
|
-
this.notifyGlobalListeners();
|
|
1327
|
-
return true;
|
|
2264
|
+
return TagManager.addTag(this, key, tag);
|
|
1328
2265
|
}
|
|
1329
2266
|
/**
|
|
1330
2267
|
* Remove a content tag from a key.
|
|
1331
2268
|
* @returns true if the tag was removed
|
|
1332
2269
|
*/
|
|
1333
2270
|
removeTag(key, tag) {
|
|
1334
|
-
|
|
1335
|
-
return false;
|
|
1336
|
-
}
|
|
1337
|
-
const entryMap = this.rootMap.get(key);
|
|
1338
|
-
if (!entryMap) {
|
|
1339
|
-
return false;
|
|
1340
|
-
}
|
|
1341
|
-
const attributes = entryMap.get("attributes");
|
|
1342
|
-
const contentTags = attributes?.contentTags || [];
|
|
1343
|
-
const tagIndex = contentTags.indexOf(tag);
|
|
1344
|
-
if (tagIndex === -1) {
|
|
1345
|
-
return false;
|
|
1346
|
-
}
|
|
1347
|
-
this.doc.transact(() => {
|
|
1348
|
-
const newContentTags = contentTags.filter((t) => t !== tag);
|
|
1349
|
-
entryMap.set("attributes", {
|
|
1350
|
-
...attributes,
|
|
1351
|
-
contentTags: newContentTags,
|
|
1352
|
-
tags: newContentTags
|
|
1353
|
-
// Sync legacy tags array
|
|
1354
|
-
});
|
|
1355
|
-
});
|
|
1356
|
-
this.notifyGlobalListeners();
|
|
1357
|
-
return true;
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* Get all content tags for a key.
|
|
1361
|
-
*/
|
|
1362
|
-
getTags(key) {
|
|
1363
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1364
|
-
return [];
|
|
1365
|
-
}
|
|
1366
|
-
const entryMap = this.rootMap.get(key);
|
|
1367
|
-
if (!entryMap) {
|
|
1368
|
-
return [];
|
|
1369
|
-
}
|
|
1370
|
-
const attributes = entryMap.get("attributes");
|
|
1371
|
-
return attributes?.contentTags || [];
|
|
1372
|
-
}
|
|
1373
|
-
/**
|
|
1374
|
-
* Get all unique content tags across all keys.
|
|
1375
|
-
*/
|
|
1376
|
-
getAllTags() {
|
|
1377
|
-
const allTags = /* @__PURE__ */ new Set();
|
|
1378
|
-
for (const [, val] of this.rootMap) {
|
|
1379
|
-
const entryMap = val;
|
|
1380
|
-
const attributes = entryMap.get("attributes");
|
|
1381
|
-
if (attributes?.contentTags) {
|
|
1382
|
-
attributes.contentTags.forEach((tag) => allTags.add(tag));
|
|
1383
|
-
}
|
|
1384
|
-
}
|
|
1385
|
-
return Array.from(allTags);
|
|
1386
|
-
}
|
|
1387
|
-
/**
|
|
1388
|
-
* Check if a key has a specific content tag.
|
|
1389
|
-
*/
|
|
1390
|
-
hasTag(key, tag) {
|
|
1391
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1392
|
-
return false;
|
|
1393
|
-
}
|
|
1394
|
-
const entryMap = this.rootMap.get(key);
|
|
1395
|
-
if (!entryMap) {
|
|
1396
|
-
return false;
|
|
1397
|
-
}
|
|
1398
|
-
const attributes = entryMap.get("attributes");
|
|
1399
|
-
return attributes?.contentTags?.includes(tag) || false;
|
|
1400
|
-
}
|
|
1401
|
-
/**
|
|
1402
|
-
* Get all keys with a specific content tag as formatted string.
|
|
1403
|
-
*/
|
|
1404
|
-
getTagged(tag) {
|
|
1405
|
-
const entries = [];
|
|
1406
|
-
const keys = this.getSortedKeys();
|
|
1407
|
-
keys.forEach((key) => {
|
|
1408
|
-
if (this.hasTag(key, tag)) {
|
|
1409
|
-
entries.push([key, this.get_value(key)]);
|
|
1410
|
-
}
|
|
1411
|
-
});
|
|
1412
|
-
return entries.map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
2271
|
+
return TagManager.removeTag(this, key, tag);
|
|
1413
2272
|
}
|
|
1414
|
-
/**
|
|
1415
|
-
* Get
|
|
1416
|
-
*/
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
return keys.filter((key) => this.hasTag(key, tag));
|
|
1420
|
-
}
|
|
1421
|
-
// ============================================
|
|
1422
|
-
// System Tag Methods (requires system access level)
|
|
1423
|
-
// ============================================
|
|
1424
|
-
/**
|
|
1425
|
-
* Add a system tag to a key (requires system access).
|
|
1426
|
-
* System tags: 'SystemPrompt', 'LLMRead', 'LLMWrite', 'readonly', 'protected', 'ApplyTemplate'
|
|
1427
|
-
*/
|
|
1428
|
-
systemAddTag(key, tag) {
|
|
1429
|
-
if (!this.hasSystemAccess) {
|
|
1430
|
-
console.warn("MindCache: systemAddTag requires system access level");
|
|
1431
|
-
return false;
|
|
1432
|
-
}
|
|
1433
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1434
|
-
return false;
|
|
1435
|
-
}
|
|
1436
|
-
const entryMap = this.rootMap.get(key);
|
|
1437
|
-
if (!entryMap) {
|
|
1438
|
-
return false;
|
|
1439
|
-
}
|
|
1440
|
-
const attributes = entryMap.get("attributes");
|
|
1441
|
-
const systemTags = attributes?.systemTags || [];
|
|
1442
|
-
if (systemTags.includes(tag)) {
|
|
1443
|
-
return false;
|
|
1444
|
-
}
|
|
1445
|
-
this.doc.transact(() => {
|
|
1446
|
-
const newSystemTags = [...systemTags, tag];
|
|
1447
|
-
const normalizedTags = this.normalizeSystemTags(newSystemTags);
|
|
1448
|
-
entryMap.set("attributes", {
|
|
1449
|
-
...attributes,
|
|
1450
|
-
systemTags: normalizedTags
|
|
1451
|
-
});
|
|
1452
|
-
});
|
|
1453
|
-
this.notifyGlobalListeners();
|
|
1454
|
-
return true;
|
|
1455
|
-
}
|
|
1456
|
-
/**
|
|
1457
|
-
* Remove a system tag from a key (requires system access).
|
|
1458
|
-
*/
|
|
1459
|
-
systemRemoveTag(key, tag) {
|
|
1460
|
-
if (!this.hasSystemAccess) {
|
|
1461
|
-
console.warn("MindCache: systemRemoveTag requires system access level");
|
|
1462
|
-
return false;
|
|
1463
|
-
}
|
|
1464
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1465
|
-
return false;
|
|
1466
|
-
}
|
|
1467
|
-
const entryMap = this.rootMap.get(key);
|
|
1468
|
-
if (!entryMap) {
|
|
1469
|
-
return false;
|
|
1470
|
-
}
|
|
1471
|
-
const attributes = entryMap.get("attributes");
|
|
1472
|
-
const systemTags = attributes?.systemTags || [];
|
|
1473
|
-
const tagIndex = systemTags.indexOf(tag);
|
|
1474
|
-
if (tagIndex === -1) {
|
|
1475
|
-
return false;
|
|
1476
|
-
}
|
|
1477
|
-
this.doc.transact(() => {
|
|
1478
|
-
const newSystemTags = systemTags.filter((t) => t !== tag);
|
|
1479
|
-
entryMap.set("attributes", {
|
|
1480
|
-
...attributes,
|
|
1481
|
-
systemTags: newSystemTags
|
|
1482
|
-
});
|
|
1483
|
-
});
|
|
1484
|
-
this.notifyGlobalListeners();
|
|
1485
|
-
return true;
|
|
2273
|
+
/**
|
|
2274
|
+
* Get all content tags for a key.
|
|
2275
|
+
*/
|
|
2276
|
+
getTags(key) {
|
|
2277
|
+
return TagManager.getTags(this, key);
|
|
1486
2278
|
}
|
|
1487
2279
|
/**
|
|
1488
|
-
* Get all
|
|
2280
|
+
* Get all unique content tags across all keys.
|
|
1489
2281
|
*/
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
console.warn("MindCache: systemGetTags requires system access level");
|
|
1493
|
-
return [];
|
|
1494
|
-
}
|
|
1495
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1496
|
-
return [];
|
|
1497
|
-
}
|
|
1498
|
-
const entryMap = this.rootMap.get(key);
|
|
1499
|
-
if (!entryMap) {
|
|
1500
|
-
return [];
|
|
1501
|
-
}
|
|
1502
|
-
const attributes = entryMap.get("attributes");
|
|
1503
|
-
return attributes?.systemTags || [];
|
|
2282
|
+
getAllTags() {
|
|
2283
|
+
return TagManager.getAllTags(this);
|
|
1504
2284
|
}
|
|
1505
2285
|
/**
|
|
1506
|
-
* Check if a key has a specific
|
|
2286
|
+
* Check if a key has a specific content tag.
|
|
1507
2287
|
*/
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
console.warn("MindCache: systemHasTag requires system access level");
|
|
1511
|
-
return false;
|
|
1512
|
-
}
|
|
1513
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1514
|
-
return false;
|
|
1515
|
-
}
|
|
1516
|
-
const entryMap = this.rootMap.get(key);
|
|
1517
|
-
if (!entryMap) {
|
|
1518
|
-
return false;
|
|
1519
|
-
}
|
|
1520
|
-
const attributes = entryMap.get("attributes");
|
|
1521
|
-
return attributes?.systemTags?.includes(tag) || false;
|
|
2288
|
+
hasTag(key, tag) {
|
|
2289
|
+
return TagManager.hasTag(this, key, tag);
|
|
1522
2290
|
}
|
|
1523
2291
|
/**
|
|
1524
|
-
*
|
|
2292
|
+
* Get all keys with a specific content tag as formatted string.
|
|
1525
2293
|
*/
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
console.warn("MindCache: systemSetTags requires system access level");
|
|
1529
|
-
return false;
|
|
1530
|
-
}
|
|
1531
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1532
|
-
return false;
|
|
1533
|
-
}
|
|
1534
|
-
const entryMap = this.rootMap.get(key);
|
|
1535
|
-
if (!entryMap) {
|
|
1536
|
-
return false;
|
|
1537
|
-
}
|
|
1538
|
-
this.doc.transact(() => {
|
|
1539
|
-
const attributes = entryMap.get("attributes");
|
|
1540
|
-
entryMap.set("attributes", {
|
|
1541
|
-
...attributes,
|
|
1542
|
-
systemTags: [...tags]
|
|
1543
|
-
});
|
|
1544
|
-
});
|
|
1545
|
-
this.notifyGlobalListeners();
|
|
1546
|
-
return true;
|
|
2294
|
+
getTagged(tag) {
|
|
2295
|
+
return TagManager.getTagged(this, tag);
|
|
1547
2296
|
}
|
|
1548
2297
|
/**
|
|
1549
|
-
* Get
|
|
2298
|
+
* Get array of keys with a specific content tag.
|
|
1550
2299
|
*/
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
console.warn("MindCache: systemGetKeysByTag requires system access level");
|
|
1554
|
-
return [];
|
|
1555
|
-
}
|
|
1556
|
-
const keys = this.getSortedKeys();
|
|
1557
|
-
return keys.filter((key) => this.systemHasTag(key, tag));
|
|
2300
|
+
getKeysByTag(tag) {
|
|
2301
|
+
return TagManager.getKeysByTag(this, tag);
|
|
1558
2302
|
}
|
|
2303
|
+
// ============================================
|
|
2304
|
+
// System Tag Methods (requires system access level)
|
|
2305
|
+
// ============================================
|
|
1559
2306
|
/**
|
|
1560
|
-
*
|
|
2307
|
+
* Add a system tag to a key (requires system access).
|
|
2308
|
+
* System tags: 'SystemPrompt', 'LLMRead', 'LLMWrite', 'ApplyTemplate'
|
|
1561
2309
|
*/
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
for (const [key, val] of this.rootMap) {
|
|
1565
|
-
const entryMap = val;
|
|
1566
|
-
const attributes = entryMap.get("attributes");
|
|
1567
|
-
entries.push({ key, zIndex: attributes?.zIndex ?? 0 });
|
|
1568
|
-
}
|
|
1569
|
-
return entries.sort((a, b) => a.zIndex - b.zIndex).map((e) => e.key);
|
|
2310
|
+
systemAddTag(key, tag) {
|
|
2311
|
+
return TagManager.systemAddTag(this, key, tag);
|
|
1570
2312
|
}
|
|
1571
2313
|
/**
|
|
1572
|
-
*
|
|
2314
|
+
* Remove a system tag from a key (requires system access).
|
|
1573
2315
|
*/
|
|
1574
|
-
|
|
1575
|
-
return
|
|
2316
|
+
systemRemoveTag(key, tag) {
|
|
2317
|
+
return TagManager.systemRemoveTag(this, key, tag);
|
|
1576
2318
|
}
|
|
1577
2319
|
/**
|
|
1578
|
-
*
|
|
2320
|
+
* Get all system tags for a key (requires system access).
|
|
1579
2321
|
*/
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
const data = JSON.parse(jsonString);
|
|
1583
|
-
this.deserialize(data);
|
|
1584
|
-
} catch (error) {
|
|
1585
|
-
console.error("MindCache: Failed to deserialize JSON:", error);
|
|
1586
|
-
}
|
|
2322
|
+
systemGetTags(key) {
|
|
2323
|
+
return TagManager.systemGetTags(this, key);
|
|
1587
2324
|
}
|
|
1588
2325
|
/**
|
|
1589
|
-
*
|
|
2326
|
+
* Check if a key has a specific system tag (requires system access).
|
|
1590
2327
|
*/
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
const lines = [];
|
|
1594
|
-
const appendixEntries = [];
|
|
1595
|
-
let appendixCounter = 0;
|
|
1596
|
-
lines.push("# MindCache STM Export");
|
|
1597
|
-
lines.push("");
|
|
1598
|
-
lines.push(`Export Date: ${now.toISOString().split("T")[0]}`);
|
|
1599
|
-
lines.push("");
|
|
1600
|
-
lines.push("---");
|
|
1601
|
-
lines.push("");
|
|
1602
|
-
lines.push("## STM Entries");
|
|
1603
|
-
lines.push("");
|
|
1604
|
-
const sortedKeys = this.getSortedKeys();
|
|
1605
|
-
sortedKeys.forEach((key) => {
|
|
1606
|
-
const entryMap = this.rootMap.get(key);
|
|
1607
|
-
if (!entryMap) {
|
|
1608
|
-
return;
|
|
1609
|
-
}
|
|
1610
|
-
const attributes = entryMap.get("attributes");
|
|
1611
|
-
const value = entryMap.get("value");
|
|
1612
|
-
if (attributes?.systemTags?.includes("protected")) {
|
|
1613
|
-
return;
|
|
1614
|
-
}
|
|
1615
|
-
lines.push(`### ${key}`);
|
|
1616
|
-
const entryType = attributes?.type || "text";
|
|
1617
|
-
lines.push(`- **Type**: \`${entryType}\``);
|
|
1618
|
-
lines.push(`- **System Tags**: \`${attributes?.systemTags?.join(", ") || "none"}\``);
|
|
1619
|
-
lines.push(`- **Z-Index**: \`${attributes?.zIndex ?? 0}\``);
|
|
1620
|
-
if (attributes?.contentTags && attributes.contentTags.length > 0) {
|
|
1621
|
-
lines.push(`- **Tags**: \`${attributes.contentTags.join("`, `")}\``);
|
|
1622
|
-
}
|
|
1623
|
-
if (attributes?.contentType) {
|
|
1624
|
-
lines.push(`- **Content Type**: \`${attributes.contentType}\``);
|
|
1625
|
-
}
|
|
1626
|
-
if (entryType === "image" || entryType === "file") {
|
|
1627
|
-
const label = String.fromCharCode(65 + appendixCounter);
|
|
1628
|
-
appendixCounter++;
|
|
1629
|
-
lines.push(`- **Value**: [See Appendix ${label}]`);
|
|
1630
|
-
appendixEntries.push({
|
|
1631
|
-
key,
|
|
1632
|
-
type: entryType,
|
|
1633
|
-
contentType: attributes?.contentType || "application/octet-stream",
|
|
1634
|
-
base64: value,
|
|
1635
|
-
label
|
|
1636
|
-
});
|
|
1637
|
-
} else if (entryType === "json") {
|
|
1638
|
-
lines.push("- **Value**:");
|
|
1639
|
-
lines.push("```json");
|
|
1640
|
-
try {
|
|
1641
|
-
const jsonValue = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
1642
|
-
lines.push(jsonValue);
|
|
1643
|
-
} catch {
|
|
1644
|
-
lines.push(String(value));
|
|
1645
|
-
}
|
|
1646
|
-
lines.push("```");
|
|
1647
|
-
} else {
|
|
1648
|
-
lines.push("- **Value**:");
|
|
1649
|
-
lines.push("```");
|
|
1650
|
-
lines.push(String(value));
|
|
1651
|
-
lines.push("```");
|
|
1652
|
-
}
|
|
1653
|
-
lines.push("");
|
|
1654
|
-
});
|
|
1655
|
-
if (appendixEntries.length > 0) {
|
|
1656
|
-
lines.push("---");
|
|
1657
|
-
lines.push("");
|
|
1658
|
-
lines.push("## Appendix: Binary Data");
|
|
1659
|
-
lines.push("");
|
|
1660
|
-
appendixEntries.forEach((entry) => {
|
|
1661
|
-
lines.push(`### Appendix ${entry.label}: ${entry.key}`);
|
|
1662
|
-
lines.push(`- **Type**: \`${entry.type}\``);
|
|
1663
|
-
lines.push(`- **Content Type**: \`${entry.contentType}\``);
|
|
1664
|
-
lines.push("- **Base64 Data**:");
|
|
1665
|
-
lines.push("```");
|
|
1666
|
-
lines.push(entry.base64);
|
|
1667
|
-
lines.push("```");
|
|
1668
|
-
lines.push("");
|
|
1669
|
-
});
|
|
1670
|
-
}
|
|
1671
|
-
return lines.join("\n");
|
|
2328
|
+
systemHasTag(key, tag) {
|
|
2329
|
+
return TagManager.systemHasTag(this, key, tag);
|
|
1672
2330
|
}
|
|
1673
2331
|
/**
|
|
1674
|
-
*
|
|
2332
|
+
* Set all system tags for a key at once (requires system access).
|
|
1675
2333
|
*/
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
if (line.startsWith("### Appendix ")) {
|
|
1694
|
-
const match = line.match(/### Appendix ([A-Z]): (.+)/);
|
|
1695
|
-
if (match) {
|
|
1696
|
-
currentKey = match[2];
|
|
1697
|
-
}
|
|
1698
|
-
continue;
|
|
1699
|
-
}
|
|
1700
|
-
if (line.startsWith("- **Type**:")) {
|
|
1701
|
-
const type = line.match(/`(.+)`/)?.[1];
|
|
1702
|
-
if (type) {
|
|
1703
|
-
currentAttributes.type = type;
|
|
1704
|
-
}
|
|
1705
|
-
continue;
|
|
1706
|
-
}
|
|
1707
|
-
if (line.startsWith("- **System Tags**:")) {
|
|
1708
|
-
const tagsStr = line.match(/`([^`]+)`/)?.[1] || "";
|
|
1709
|
-
if (tagsStr !== "none") {
|
|
1710
|
-
currentAttributes.systemTags = tagsStr.split(", ").filter((t) => t);
|
|
1711
|
-
}
|
|
1712
|
-
continue;
|
|
1713
|
-
}
|
|
1714
|
-
if (line.startsWith("- **Z-Index**:")) {
|
|
1715
|
-
const zIndex = parseInt(line.match(/`(\d+)`/)?.[1] || "0", 10);
|
|
1716
|
-
currentAttributes.zIndex = zIndex;
|
|
1717
|
-
continue;
|
|
1718
|
-
}
|
|
1719
|
-
if (line.startsWith("- **Tags**:")) {
|
|
1720
|
-
const tags = line.match(/`([^`]+)`/g)?.map((t) => t.slice(1, -1)) || [];
|
|
1721
|
-
currentAttributes.contentTags = tags;
|
|
1722
|
-
continue;
|
|
1723
|
-
}
|
|
1724
|
-
if (line.startsWith("- **Content Type**:")) {
|
|
1725
|
-
currentAttributes.contentType = line.match(/`(.+)`/)?.[1];
|
|
1726
|
-
continue;
|
|
1727
|
-
}
|
|
1728
|
-
if (line.startsWith("- **Value**:") && !line.includes("[See Appendix")) {
|
|
1729
|
-
const afterValue = line.substring(12).trim();
|
|
1730
|
-
if (afterValue === "") {
|
|
1731
|
-
currentValue = "";
|
|
1732
|
-
} else if (afterValue === "```" || afterValue === "```json") {
|
|
1733
|
-
inCodeBlock = true;
|
|
1734
|
-
codeBlockContent = [];
|
|
1735
|
-
currentValue = "";
|
|
1736
|
-
} else if (afterValue.startsWith("```")) {
|
|
1737
|
-
inCodeBlock = true;
|
|
1738
|
-
codeBlockContent = [afterValue.substring(3)];
|
|
1739
|
-
currentValue = "";
|
|
1740
|
-
} else {
|
|
1741
|
-
currentValue = afterValue;
|
|
1742
|
-
}
|
|
1743
|
-
continue;
|
|
1744
|
-
}
|
|
1745
|
-
const trimmedLine = line.trim();
|
|
1746
|
-
if (trimmedLine === "```json" || trimmedLine === "```") {
|
|
1747
|
-
if (inCodeBlock) {
|
|
1748
|
-
inCodeBlock = false;
|
|
1749
|
-
if (currentKey && codeBlockContent.length > 0) {
|
|
1750
|
-
currentValue = codeBlockContent.join("\n");
|
|
1751
|
-
}
|
|
1752
|
-
codeBlockContent = [];
|
|
1753
|
-
} else {
|
|
1754
|
-
inCodeBlock = true;
|
|
1755
|
-
codeBlockContent = [];
|
|
1756
|
-
}
|
|
2334
|
+
systemSetTags(key, tags) {
|
|
2335
|
+
return TagManager.systemSetTags(this, key, tags);
|
|
2336
|
+
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Get all keys with a specific system tag (requires system access).
|
|
2339
|
+
*/
|
|
2340
|
+
systemGetKeysByTag(tag) {
|
|
2341
|
+
return TagManager.systemGetKeysByTag(this, tag);
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Helper to get sorted keys (by zIndex).
|
|
2345
|
+
* Respects context filtering when set.
|
|
2346
|
+
*/
|
|
2347
|
+
getSortedKeys() {
|
|
2348
|
+
const entries = [];
|
|
2349
|
+
for (const [key, val] of this.rootMap) {
|
|
2350
|
+
if (!this.keyMatchesContext(key)) {
|
|
1757
2351
|
continue;
|
|
1758
2352
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
currentValue += "\n" + line;
|
|
1763
|
-
}
|
|
1764
|
-
}
|
|
1765
|
-
if (currentKey && currentValue !== null) {
|
|
1766
|
-
this.set_value(currentKey, currentValue.trim(), currentAttributes);
|
|
2353
|
+
const entryMap = val;
|
|
2354
|
+
const attributes = entryMap.get("attributes");
|
|
2355
|
+
entries.push({ key, zIndex: attributes?.zIndex ?? 0 });
|
|
1767
2356
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
2357
|
+
return entries.sort((a, b) => a.zIndex - b.zIndex).map((e) => e.key);
|
|
2358
|
+
}
|
|
2359
|
+
/**
|
|
2360
|
+
* Serialize to JSON string.
|
|
2361
|
+
*/
|
|
2362
|
+
toJSON() {
|
|
2363
|
+
return JSON.stringify(this.serialize());
|
|
2364
|
+
}
|
|
2365
|
+
/**
|
|
2366
|
+
* Deserialize from JSON string.
|
|
2367
|
+
*/
|
|
2368
|
+
fromJSON(jsonString) {
|
|
2369
|
+
try {
|
|
2370
|
+
const data = JSON.parse(jsonString);
|
|
2371
|
+
this.deserialize(data);
|
|
2372
|
+
} catch (error) {
|
|
2373
|
+
console.error("MindCache: Failed to deserialize JSON:", error);
|
|
1776
2374
|
}
|
|
1777
2375
|
}
|
|
2376
|
+
/**
|
|
2377
|
+
* Export to Markdown format.
|
|
2378
|
+
*/
|
|
2379
|
+
toMarkdown() {
|
|
2380
|
+
return MarkdownSerializer.toMarkdown(this);
|
|
2381
|
+
}
|
|
2382
|
+
/**
|
|
2383
|
+
* Import from Markdown format.
|
|
2384
|
+
* @param markdown The markdown string to import
|
|
2385
|
+
* @param merge If false (default), clears existing data before importing. If true, merges with existing data.
|
|
2386
|
+
*/
|
|
2387
|
+
fromMarkdown(markdown, merge = false) {
|
|
2388
|
+
MarkdownSerializer.fromMarkdown(markdown, this, merge);
|
|
2389
|
+
}
|
|
1778
2390
|
/**
|
|
1779
2391
|
* Set base64 binary data.
|
|
1780
2392
|
*/
|
|
@@ -1855,9 +2467,6 @@ var MindCache = class {
|
|
|
1855
2467
|
* Note: This exposes Yjs Y.Text directly for editor bindings (y-quill, y-codemirror, etc.)
|
|
1856
2468
|
*/
|
|
1857
2469
|
set_document(key, initialText, attributes) {
|
|
1858
|
-
if (key === "$date" || key === "$time" || key === "$version") {
|
|
1859
|
-
return;
|
|
1860
|
-
}
|
|
1861
2470
|
let entryMap = this.rootMap.get(key);
|
|
1862
2471
|
if (!entryMap) {
|
|
1863
2472
|
entryMap = new Y__namespace.Map();
|
|
@@ -1913,223 +2522,95 @@ var MindCache = class {
|
|
|
1913
2522
|
const yText = this.get_document(key);
|
|
1914
2523
|
if (yText) {
|
|
1915
2524
|
yText.delete(index, length2);
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
/**
|
|
1919
|
-
* Replace all text in a document key
|
|
1920
|
-
* Uses diff-based updates when changes are < diffThreshold (default 80%).
|
|
1921
|
-
* This preserves concurrent edits and provides better undo granularity.
|
|
1922
|
-
*/
|
|
1923
|
-
_replaceDocumentText(key, newText, diffThreshold = 0.8) {
|
|
1924
|
-
const yText = this.get_document(key);
|
|
1925
|
-
if (!yText) {
|
|
1926
|
-
return;
|
|
1927
|
-
}
|
|
1928
|
-
const oldText = yText.toString();
|
|
1929
|
-
if (oldText === newText) {
|
|
1930
|
-
return;
|
|
1931
|
-
}
|
|
1932
|
-
if (oldText.length === 0) {
|
|
1933
|
-
yText.insert(0, newText);
|
|
1934
|
-
return;
|
|
1935
|
-
}
|
|
1936
|
-
const diffs = diff__default.default(oldText, newText);
|
|
1937
|
-
let changedChars = 0;
|
|
1938
|
-
for (const [op, text] of diffs) {
|
|
1939
|
-
if (op !== 0) {
|
|
1940
|
-
changedChars += text.length;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
const changeRatio = changedChars / Math.max(oldText.length, newText.length);
|
|
1944
|
-
if (changeRatio > diffThreshold) {
|
|
1945
|
-
this.doc.transact(() => {
|
|
1946
|
-
yText.delete(0, yText.length);
|
|
1947
|
-
yText.insert(0, newText);
|
|
1948
|
-
});
|
|
1949
|
-
return;
|
|
1950
|
-
}
|
|
1951
|
-
this.doc.transact(() => {
|
|
1952
|
-
let cursor = 0;
|
|
1953
|
-
for (const [op, text] of diffs) {
|
|
1954
|
-
if (op === 0) {
|
|
1955
|
-
cursor += text.length;
|
|
1956
|
-
} else if (op === -1) {
|
|
1957
|
-
yText.delete(cursor, text.length);
|
|
1958
|
-
} else if (op === 1) {
|
|
1959
|
-
yText.insert(cursor, text);
|
|
1960
|
-
cursor += text.length;
|
|
1961
|
-
}
|
|
1962
|
-
}
|
|
1963
|
-
});
|
|
1964
|
-
}
|
|
1965
|
-
// ... (subscribe methods)
|
|
1966
|
-
subscribe(key, listener) {
|
|
1967
|
-
if (!this.listeners[key]) {
|
|
1968
|
-
this.listeners[key] = [];
|
|
1969
|
-
}
|
|
1970
|
-
this.listeners[key].push(listener);
|
|
1971
|
-
return () => {
|
|
1972
|
-
this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
subscribeToAll(listener) {
|
|
1976
|
-
this.globalListeners.push(listener);
|
|
1977
|
-
return () => {
|
|
1978
|
-
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
1979
|
-
};
|
|
1980
|
-
}
|
|
1981
|
-
unsubscribeFromAll(listener) {
|
|
1982
|
-
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
1983
|
-
}
|
|
1984
|
-
notifyGlobalListeners() {
|
|
1985
|
-
this.globalListeners.forEach((l) => l());
|
|
1986
|
-
}
|
|
1987
|
-
// Sanitize key name for use in tool names
|
|
1988
|
-
sanitizeKeyForTool(key) {
|
|
1989
|
-
return key.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1990
|
-
}
|
|
1991
|
-
// Find original key from sanitized tool name
|
|
1992
|
-
findKeyFromSanitizedTool(sanitizedKey) {
|
|
1993
|
-
for (const [key] of this.rootMap) {
|
|
1994
|
-
if (this.sanitizeKeyForTool(key) === sanitizedKey) {
|
|
1995
|
-
return key;
|
|
1996
|
-
}
|
|
1997
|
-
}
|
|
1998
|
-
return void 0;
|
|
1999
|
-
}
|
|
2000
|
-
/**
|
|
2001
|
-
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
2002
|
-
* For document type keys, generates additional tools: append_, insert_, edit_
|
|
2003
|
-
*
|
|
2004
|
-
* Security: All tools use llm_set_key internally which:
|
|
2005
|
-
* - Only modifies VALUES, never attributes/systemTags
|
|
2006
|
-
* - Prevents LLMs from escalating privileges
|
|
2007
|
-
*/
|
|
2008
|
-
create_vercel_ai_tools() {
|
|
2009
|
-
const tools = {};
|
|
2010
|
-
for (const [key, val] of this.rootMap) {
|
|
2011
|
-
if (key.startsWith("$")) {
|
|
2012
|
-
continue;
|
|
2013
|
-
}
|
|
2014
|
-
const entryMap = val;
|
|
2015
|
-
const attributes = entryMap.get("attributes");
|
|
2016
|
-
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
2017
|
-
if (!isWritable) {
|
|
2018
|
-
continue;
|
|
2019
|
-
}
|
|
2020
|
-
const sanitizedKey = this.sanitizeKeyForTool(key);
|
|
2021
|
-
const isDocument = attributes?.type === "document";
|
|
2022
|
-
tools[`write_${sanitizedKey}`] = {
|
|
2023
|
-
description: isDocument ? `Rewrite the entire "${key}" document` : `Write a value to the STM key: ${key}`,
|
|
2024
|
-
inputSchema: {
|
|
2025
|
-
type: "object",
|
|
2026
|
-
properties: {
|
|
2027
|
-
value: { type: "string", description: isDocument ? "New document content" : "The value to write" }
|
|
2028
|
-
},
|
|
2029
|
-
required: ["value"]
|
|
2030
|
-
},
|
|
2031
|
-
execute: async ({ value }) => {
|
|
2032
|
-
const success = this.llm_set_key(key, value);
|
|
2033
|
-
if (success) {
|
|
2034
|
-
return {
|
|
2035
|
-
result: `Successfully wrote "${value}" to ${key}`,
|
|
2036
|
-
key,
|
|
2037
|
-
value
|
|
2038
|
-
};
|
|
2039
|
-
}
|
|
2040
|
-
return {
|
|
2041
|
-
result: `Failed to write to ${key} - permission denied or key not found`,
|
|
2042
|
-
key,
|
|
2043
|
-
error: true
|
|
2044
|
-
};
|
|
2045
|
-
}
|
|
2046
|
-
};
|
|
2047
|
-
if (isDocument) {
|
|
2048
|
-
tools[`append_${sanitizedKey}`] = {
|
|
2049
|
-
description: `Append text to the end of "${key}" document`,
|
|
2050
|
-
inputSchema: {
|
|
2051
|
-
type: "object",
|
|
2052
|
-
properties: {
|
|
2053
|
-
text: { type: "string", description: "Text to append" }
|
|
2054
|
-
},
|
|
2055
|
-
required: ["text"]
|
|
2056
|
-
},
|
|
2057
|
-
execute: async ({ text }) => {
|
|
2058
|
-
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
2059
|
-
return { result: `Permission denied for ${key}`, key, error: true };
|
|
2060
|
-
}
|
|
2061
|
-
const yText = this.get_document(key);
|
|
2062
|
-
if (yText) {
|
|
2063
|
-
yText.insert(yText.length, text);
|
|
2064
|
-
return {
|
|
2065
|
-
result: `Successfully appended to ${key}`,
|
|
2066
|
-
key,
|
|
2067
|
-
appended: text
|
|
2068
|
-
};
|
|
2069
|
-
}
|
|
2070
|
-
return { result: `Document ${key} not found`, key };
|
|
2071
|
-
}
|
|
2072
|
-
};
|
|
2073
|
-
tools[`insert_${sanitizedKey}`] = {
|
|
2074
|
-
description: `Insert text at a position in "${key}" document`,
|
|
2075
|
-
inputSchema: {
|
|
2076
|
-
type: "object",
|
|
2077
|
-
properties: {
|
|
2078
|
-
index: { type: "number", description: "Position to insert at (0 = start)" },
|
|
2079
|
-
text: { type: "string", description: "Text to insert" }
|
|
2080
|
-
},
|
|
2081
|
-
required: ["index", "text"]
|
|
2082
|
-
},
|
|
2083
|
-
execute: async ({ index, text }) => {
|
|
2084
|
-
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
2085
|
-
return { result: `Permission denied for ${key}`, key, error: true };
|
|
2086
|
-
}
|
|
2087
|
-
this.insert_text(key, index, text);
|
|
2088
|
-
return {
|
|
2089
|
-
result: `Successfully inserted text at position ${index} in ${key}`,
|
|
2090
|
-
key,
|
|
2091
|
-
index,
|
|
2092
|
-
inserted: text
|
|
2093
|
-
};
|
|
2094
|
-
}
|
|
2095
|
-
};
|
|
2096
|
-
tools[`edit_${sanitizedKey}`] = {
|
|
2097
|
-
description: `Find and replace text in "${key}" document`,
|
|
2098
|
-
inputSchema: {
|
|
2099
|
-
type: "object",
|
|
2100
|
-
properties: {
|
|
2101
|
-
find: { type: "string", description: "Text to find" },
|
|
2102
|
-
replace: { type: "string", description: "Replacement text" }
|
|
2103
|
-
},
|
|
2104
|
-
required: ["find", "replace"]
|
|
2105
|
-
},
|
|
2106
|
-
execute: async ({ find, replace }) => {
|
|
2107
|
-
if (!attributes?.systemTags?.includes("LLMWrite")) {
|
|
2108
|
-
return { result: `Permission denied for ${key}`, key, error: true };
|
|
2109
|
-
}
|
|
2110
|
-
const yText = this.get_document(key);
|
|
2111
|
-
if (yText) {
|
|
2112
|
-
const text = yText.toString();
|
|
2113
|
-
const idx = text.indexOf(find);
|
|
2114
|
-
if (idx !== -1) {
|
|
2115
|
-
yText.delete(idx, find.length);
|
|
2116
|
-
yText.insert(idx, replace);
|
|
2117
|
-
return {
|
|
2118
|
-
result: `Successfully replaced "${find}" with "${replace}" in ${key}`,
|
|
2119
|
-
key,
|
|
2120
|
-
find,
|
|
2121
|
-
replace,
|
|
2122
|
-
index: idx
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
return { result: `Text "${find}" not found in ${key}`, key };
|
|
2126
|
-
}
|
|
2127
|
-
return { result: `Document ${key} not found`, key };
|
|
2128
|
-
}
|
|
2129
|
-
};
|
|
2525
|
+
}
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* Replace all text in a document key.
|
|
2529
|
+
* Uses diff-based updates when changes are < diffThreshold (default 80%).
|
|
2530
|
+
* This preserves concurrent edits and provides better undo granularity.
|
|
2531
|
+
*/
|
|
2532
|
+
_replaceDocumentText(key, newText, diffThreshold = 0.8) {
|
|
2533
|
+
const yText = this.get_document(key);
|
|
2534
|
+
if (!yText) {
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2537
|
+
const oldText = yText.toString();
|
|
2538
|
+
if (oldText === newText) {
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
if (oldText.length === 0) {
|
|
2542
|
+
yText.insert(0, newText);
|
|
2543
|
+
return;
|
|
2544
|
+
}
|
|
2545
|
+
const diffs = diff__default.default(oldText, newText);
|
|
2546
|
+
let changedChars = 0;
|
|
2547
|
+
for (const [op, text] of diffs) {
|
|
2548
|
+
if (op !== 0) {
|
|
2549
|
+
changedChars += text.length;
|
|
2130
2550
|
}
|
|
2131
2551
|
}
|
|
2132
|
-
|
|
2552
|
+
const changeRatio = changedChars / Math.max(oldText.length, newText.length);
|
|
2553
|
+
if (changeRatio > diffThreshold) {
|
|
2554
|
+
this.doc.transact(() => {
|
|
2555
|
+
yText.delete(0, yText.length);
|
|
2556
|
+
yText.insert(0, newText);
|
|
2557
|
+
});
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
this.doc.transact(() => {
|
|
2561
|
+
let cursor = 0;
|
|
2562
|
+
for (const [op, text] of diffs) {
|
|
2563
|
+
if (op === 0) {
|
|
2564
|
+
cursor += text.length;
|
|
2565
|
+
} else if (op === -1) {
|
|
2566
|
+
yText.delete(cursor, text.length);
|
|
2567
|
+
} else if (op === 1) {
|
|
2568
|
+
yText.insert(cursor, text);
|
|
2569
|
+
cursor += text.length;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
});
|
|
2573
|
+
}
|
|
2574
|
+
// ... (subscribe methods)
|
|
2575
|
+
subscribe(key, listener) {
|
|
2576
|
+
if (!this.listeners[key]) {
|
|
2577
|
+
this.listeners[key] = [];
|
|
2578
|
+
}
|
|
2579
|
+
this.listeners[key].push(listener);
|
|
2580
|
+
return () => {
|
|
2581
|
+
this.listeners[key] = this.listeners[key].filter((l) => l !== listener);
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
subscribeToAll(listener) {
|
|
2585
|
+
this.globalListeners.push(listener);
|
|
2586
|
+
return () => {
|
|
2587
|
+
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
unsubscribeFromAll(listener) {
|
|
2591
|
+
this.globalListeners = this.globalListeners.filter((l) => l !== listener);
|
|
2592
|
+
}
|
|
2593
|
+
notifyGlobalListeners() {
|
|
2594
|
+
this.globalListeners.forEach((l) => l());
|
|
2595
|
+
}
|
|
2596
|
+
// Sanitize key name for use in tool names
|
|
2597
|
+
sanitizeKeyForTool(key) {
|
|
2598
|
+
return AIToolBuilder.sanitizeKeyForTool(key);
|
|
2599
|
+
}
|
|
2600
|
+
// Find original key from sanitized tool name
|
|
2601
|
+
findKeyFromSanitizedTool(sanitizedKey) {
|
|
2602
|
+
return AIToolBuilder.findKeyFromSanitizedTool(this, sanitizedKey);
|
|
2603
|
+
}
|
|
2604
|
+
/**
|
|
2605
|
+
* Generate Vercel AI SDK compatible tools for writable keys.
|
|
2606
|
+
* For document type keys, generates additional tools: append_, insert_, edit_
|
|
2607
|
+
*
|
|
2608
|
+
* Security: All tools use llm_set_key internally which:
|
|
2609
|
+
* - Only modifies VALUES, never attributes/systemTags
|
|
2610
|
+
* - Prevents LLMs from escalating privileges
|
|
2611
|
+
*/
|
|
2612
|
+
create_vercel_ai_tools() {
|
|
2613
|
+
return AIToolBuilder.createVercelAITools(this);
|
|
2133
2614
|
}
|
|
2134
2615
|
/**
|
|
2135
2616
|
* @deprecated Use create_vercel_ai_tools() instead
|
|
@@ -2142,121 +2623,14 @@ var MindCache = class {
|
|
|
2142
2623
|
* Indicates which tools can be used to modify writable keys.
|
|
2143
2624
|
*/
|
|
2144
2625
|
get_system_prompt() {
|
|
2145
|
-
|
|
2146
|
-
for (const [key, val] of this.rootMap) {
|
|
2147
|
-
if (key.startsWith("$")) {
|
|
2148
|
-
continue;
|
|
2149
|
-
}
|
|
2150
|
-
const entryMap = val;
|
|
2151
|
-
const attributes = entryMap.get("attributes");
|
|
2152
|
-
const isVisible = attributes?.systemTags?.includes("SystemPrompt") || attributes?.systemTags?.includes("LLMRead");
|
|
2153
|
-
if (!isVisible) {
|
|
2154
|
-
continue;
|
|
2155
|
-
}
|
|
2156
|
-
const value = this.get_value(key);
|
|
2157
|
-
const displayValue = typeof value === "object" ? JSON.stringify(value) : value;
|
|
2158
|
-
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
2159
|
-
const isDocument = attributes?.type === "document";
|
|
2160
|
-
const sanitizedKey = this.sanitizeKeyForTool(key);
|
|
2161
|
-
if (isWritable) {
|
|
2162
|
-
if (isDocument) {
|
|
2163
|
-
lines.push(
|
|
2164
|
-
`${key}: ${displayValue}. Document tools: write_${sanitizedKey}, append_${sanitizedKey}, edit_${sanitizedKey}`
|
|
2165
|
-
);
|
|
2166
|
-
} else {
|
|
2167
|
-
const oldValueHint = displayValue ? ` This tool DOES NOT append \u2014 start your response with the old value (${displayValue})` : "";
|
|
2168
|
-
lines.push(
|
|
2169
|
-
`${key}: ${displayValue}. You can rewrite "${key}" by using the write_${sanitizedKey} tool.${oldValueHint}`
|
|
2170
|
-
);
|
|
2171
|
-
}
|
|
2172
|
-
} else {
|
|
2173
|
-
lines.push(`${key}: ${displayValue}`);
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
lines.push(`$date: ${this.get_value("$date")}`);
|
|
2177
|
-
lines.push(`$time: ${this.get_value("$time")}`);
|
|
2178
|
-
return lines.join(", ");
|
|
2626
|
+
return AIToolBuilder.getSystemPrompt(this);
|
|
2179
2627
|
}
|
|
2180
2628
|
/**
|
|
2181
2629
|
* Execute a tool call by name with the given value.
|
|
2182
2630
|
* Returns the result or null if tool not found.
|
|
2183
2631
|
*/
|
|
2184
2632
|
executeToolCall(toolName, value) {
|
|
2185
|
-
|
|
2186
|
-
if (!match) {
|
|
2187
|
-
return null;
|
|
2188
|
-
}
|
|
2189
|
-
const [, action, sanitizedKey] = match;
|
|
2190
|
-
const key = this.findKeyFromSanitizedTool(sanitizedKey);
|
|
2191
|
-
if (!key) {
|
|
2192
|
-
return null;
|
|
2193
|
-
}
|
|
2194
|
-
const entryMap = this.rootMap.get(key);
|
|
2195
|
-
if (!entryMap) {
|
|
2196
|
-
return null;
|
|
2197
|
-
}
|
|
2198
|
-
const attributes = entryMap.get("attributes");
|
|
2199
|
-
const isWritable = attributes?.systemTags?.includes("LLMWrite");
|
|
2200
|
-
if (!isWritable) {
|
|
2201
|
-
return null;
|
|
2202
|
-
}
|
|
2203
|
-
const isDocument = attributes?.type === "document";
|
|
2204
|
-
switch (action) {
|
|
2205
|
-
case "write":
|
|
2206
|
-
if (isDocument) {
|
|
2207
|
-
this._replaceDocumentText(key, value);
|
|
2208
|
-
} else {
|
|
2209
|
-
this.set_value(key, value);
|
|
2210
|
-
}
|
|
2211
|
-
return {
|
|
2212
|
-
result: `Successfully wrote "${value}" to ${key}`,
|
|
2213
|
-
key,
|
|
2214
|
-
value
|
|
2215
|
-
};
|
|
2216
|
-
case "append":
|
|
2217
|
-
if (isDocument) {
|
|
2218
|
-
const yText = this.get_document(key);
|
|
2219
|
-
if (yText) {
|
|
2220
|
-
yText.insert(yText.length, value);
|
|
2221
|
-
return {
|
|
2222
|
-
result: `Successfully appended to ${key}`,
|
|
2223
|
-
key,
|
|
2224
|
-
value
|
|
2225
|
-
};
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
return null;
|
|
2229
|
-
case "insert":
|
|
2230
|
-
if (isDocument && typeof value === "object" && value.index !== void 0 && value.text) {
|
|
2231
|
-
this.insert_text(key, value.index, value.text);
|
|
2232
|
-
return {
|
|
2233
|
-
result: `Successfully inserted at position ${value.index} in ${key}`,
|
|
2234
|
-
key,
|
|
2235
|
-
value: value.text
|
|
2236
|
-
};
|
|
2237
|
-
}
|
|
2238
|
-
return null;
|
|
2239
|
-
case "edit":
|
|
2240
|
-
if (isDocument && typeof value === "object" && value.find && value.replace !== void 0) {
|
|
2241
|
-
const yText = this.get_document(key);
|
|
2242
|
-
if (yText) {
|
|
2243
|
-
const text = yText.toString();
|
|
2244
|
-
const idx = text.indexOf(value.find);
|
|
2245
|
-
if (idx !== -1) {
|
|
2246
|
-
yText.delete(idx, value.find.length);
|
|
2247
|
-
yText.insert(idx, value.replace);
|
|
2248
|
-
return {
|
|
2249
|
-
result: `Successfully replaced "${value.find}" with "${value.replace}" in ${key}`,
|
|
2250
|
-
key,
|
|
2251
|
-
value: value.replace
|
|
2252
|
-
};
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
return null;
|
|
2257
|
-
default:
|
|
2258
|
-
return null;
|
|
2259
|
-
}
|
|
2633
|
+
return AIToolBuilder.executeToolCall(this, toolName, value);
|
|
2260
2634
|
}
|
|
2261
2635
|
// Internal method stub for legacy compatibility
|
|
2262
2636
|
_setFromRemote(_key, _value, _attributes) {
|
|
@@ -2271,6 +2645,325 @@ var MindCache = class {
|
|
|
2271
2645
|
init_CloudAdapter();
|
|
2272
2646
|
init_CloudAdapter();
|
|
2273
2647
|
|
|
2648
|
+
// src/cloud/OAuthClient.ts
|
|
2649
|
+
var DEFAULT_AUTH_URL = "https://api.mindcache.dev/oauth/authorize";
|
|
2650
|
+
var DEFAULT_TOKEN_URL = "https://api.mindcache.dev/oauth/token";
|
|
2651
|
+
var DEFAULT_USERINFO_URL = "https://api.mindcache.dev/oauth/userinfo";
|
|
2652
|
+
var TOKEN_REFRESH_BUFFER = 5 * 60 * 1e3;
|
|
2653
|
+
function generateRandomString(length2) {
|
|
2654
|
+
const array = new Uint8Array(length2);
|
|
2655
|
+
crypto.getRandomValues(array);
|
|
2656
|
+
return Array.from(array).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, length2);
|
|
2657
|
+
}
|
|
2658
|
+
function base64UrlEncode(buffer) {
|
|
2659
|
+
const bytes = new Uint8Array(buffer);
|
|
2660
|
+
let binary = "";
|
|
2661
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
2662
|
+
binary += String.fromCharCode(bytes[i]);
|
|
2663
|
+
}
|
|
2664
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
2665
|
+
}
|
|
2666
|
+
function generateCodeVerifier() {
|
|
2667
|
+
return generateRandomString(64);
|
|
2668
|
+
}
|
|
2669
|
+
async function generateCodeChallenge(verifier) {
|
|
2670
|
+
const encoder = new TextEncoder();
|
|
2671
|
+
const data = encoder.encode(verifier);
|
|
2672
|
+
const hash = await crypto.subtle.digest("SHA-256", data);
|
|
2673
|
+
return base64UrlEncode(hash);
|
|
2674
|
+
}
|
|
2675
|
+
var OAuthClient = class {
|
|
2676
|
+
config;
|
|
2677
|
+
tokens = null;
|
|
2678
|
+
refreshPromise = null;
|
|
2679
|
+
constructor(config) {
|
|
2680
|
+
let redirectUri = config.redirectUri;
|
|
2681
|
+
if (!redirectUri && typeof window !== "undefined") {
|
|
2682
|
+
const url = new URL(window.location.href);
|
|
2683
|
+
url.search = "";
|
|
2684
|
+
url.hash = "";
|
|
2685
|
+
redirectUri = url.toString();
|
|
2686
|
+
}
|
|
2687
|
+
this.config = {
|
|
2688
|
+
clientId: config.clientId,
|
|
2689
|
+
redirectUri: redirectUri || "",
|
|
2690
|
+
scopes: config.scopes || ["read", "write"],
|
|
2691
|
+
authUrl: config.authUrl || DEFAULT_AUTH_URL,
|
|
2692
|
+
tokenUrl: config.tokenUrl || DEFAULT_TOKEN_URL,
|
|
2693
|
+
usePKCE: config.usePKCE !== false,
|
|
2694
|
+
// Default true
|
|
2695
|
+
storagePrefix: config.storagePrefix || "mindcache_oauth"
|
|
2696
|
+
};
|
|
2697
|
+
this.loadTokens();
|
|
2698
|
+
}
|
|
2699
|
+
/**
|
|
2700
|
+
* Check if user is authenticated
|
|
2701
|
+
*/
|
|
2702
|
+
isAuthenticated() {
|
|
2703
|
+
return this.tokens !== null && this.tokens.expiresAt > Date.now();
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Get stored tokens (if any)
|
|
2707
|
+
*/
|
|
2708
|
+
getTokens() {
|
|
2709
|
+
return this.tokens;
|
|
2710
|
+
}
|
|
2711
|
+
/**
|
|
2712
|
+
* Get instance ID for this user+app
|
|
2713
|
+
*/
|
|
2714
|
+
getInstanceId() {
|
|
2715
|
+
return this.tokens?.instanceId || null;
|
|
2716
|
+
}
|
|
2717
|
+
/**
|
|
2718
|
+
* Start OAuth authorization flow
|
|
2719
|
+
* Redirects to MindCache authorization page
|
|
2720
|
+
*/
|
|
2721
|
+
async authorize(options) {
|
|
2722
|
+
const state = options?.state || generateRandomString(32);
|
|
2723
|
+
this.setStorage("state", state);
|
|
2724
|
+
const url = new URL(this.config.authUrl);
|
|
2725
|
+
url.searchParams.set("response_type", "code");
|
|
2726
|
+
url.searchParams.set("client_id", this.config.clientId);
|
|
2727
|
+
url.searchParams.set("redirect_uri", this.config.redirectUri);
|
|
2728
|
+
url.searchParams.set("scope", this.config.scopes.join(" "));
|
|
2729
|
+
url.searchParams.set("state", state);
|
|
2730
|
+
if (this.config.usePKCE) {
|
|
2731
|
+
const codeVerifier = generateCodeVerifier();
|
|
2732
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
2733
|
+
this.setStorage("code_verifier", codeVerifier);
|
|
2734
|
+
url.searchParams.set("code_challenge", codeChallenge);
|
|
2735
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
2736
|
+
}
|
|
2737
|
+
if (options?.popup) {
|
|
2738
|
+
const popup = window.open(url.toString(), "mindcache_oauth", "width=500,height=600");
|
|
2739
|
+
if (!popup) {
|
|
2740
|
+
throw new Error("Popup blocked. Please allow popups for this site.");
|
|
2741
|
+
}
|
|
2742
|
+
} else {
|
|
2743
|
+
window.location.href = url.toString();
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
/**
|
|
2747
|
+
* Handle OAuth callback
|
|
2748
|
+
* Call this on your redirect URI page
|
|
2749
|
+
*
|
|
2750
|
+
* @returns Tokens if successful
|
|
2751
|
+
*/
|
|
2752
|
+
async handleCallback() {
|
|
2753
|
+
if (typeof window === "undefined") {
|
|
2754
|
+
throw new Error("handleCallback must be called in browser");
|
|
2755
|
+
}
|
|
2756
|
+
const url = new URL(window.location.href);
|
|
2757
|
+
const code = url.searchParams.get("code");
|
|
2758
|
+
const state = url.searchParams.get("state");
|
|
2759
|
+
const error = url.searchParams.get("error");
|
|
2760
|
+
const errorDescription = url.searchParams.get("error_description");
|
|
2761
|
+
if (error) {
|
|
2762
|
+
this.clearStorage();
|
|
2763
|
+
throw new Error(errorDescription || error);
|
|
2764
|
+
}
|
|
2765
|
+
const storedState = this.getStorage("state");
|
|
2766
|
+
if (!state || state !== storedState) {
|
|
2767
|
+
this.clearStorage();
|
|
2768
|
+
throw new Error("Invalid state parameter");
|
|
2769
|
+
}
|
|
2770
|
+
if (!code) {
|
|
2771
|
+
this.clearStorage();
|
|
2772
|
+
throw new Error("No authorization code received");
|
|
2773
|
+
}
|
|
2774
|
+
const body = {
|
|
2775
|
+
grant_type: "authorization_code",
|
|
2776
|
+
code,
|
|
2777
|
+
client_id: this.config.clientId,
|
|
2778
|
+
redirect_uri: this.config.redirectUri
|
|
2779
|
+
};
|
|
2780
|
+
if (this.config.usePKCE) {
|
|
2781
|
+
const codeVerifier = this.getStorage("code_verifier");
|
|
2782
|
+
if (!codeVerifier) {
|
|
2783
|
+
throw new Error("Missing code verifier");
|
|
2784
|
+
}
|
|
2785
|
+
body.code_verifier = codeVerifier;
|
|
2786
|
+
}
|
|
2787
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
2788
|
+
method: "POST",
|
|
2789
|
+
headers: {
|
|
2790
|
+
"Content-Type": "application/json"
|
|
2791
|
+
},
|
|
2792
|
+
body: JSON.stringify(body)
|
|
2793
|
+
});
|
|
2794
|
+
if (!response.ok) {
|
|
2795
|
+
const data2 = await response.json().catch(() => ({}));
|
|
2796
|
+
throw new Error(data2.error_description || data2.error || "Token exchange failed");
|
|
2797
|
+
}
|
|
2798
|
+
const data = await response.json();
|
|
2799
|
+
this.tokens = {
|
|
2800
|
+
accessToken: data.access_token,
|
|
2801
|
+
refreshToken: data.refresh_token,
|
|
2802
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
2803
|
+
scopes: data.scope?.split(" ") || this.config.scopes,
|
|
2804
|
+
instanceId: data.instance_id
|
|
2805
|
+
};
|
|
2806
|
+
this.saveTokens();
|
|
2807
|
+
url.searchParams.delete("code");
|
|
2808
|
+
url.searchParams.delete("state");
|
|
2809
|
+
window.history.replaceState({}, "", url.toString());
|
|
2810
|
+
this.removeStorage("state");
|
|
2811
|
+
this.removeStorage("code_verifier");
|
|
2812
|
+
return this.tokens;
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* Get a valid access token
|
|
2816
|
+
* Automatically refreshes if needed
|
|
2817
|
+
*/
|
|
2818
|
+
async getAccessToken() {
|
|
2819
|
+
if (!this.tokens) {
|
|
2820
|
+
throw new Error("Not authenticated. Call authorize() first.");
|
|
2821
|
+
}
|
|
2822
|
+
const needsRefresh = this.tokens.expiresAt - Date.now() < TOKEN_REFRESH_BUFFER;
|
|
2823
|
+
if (needsRefresh && this.tokens.refreshToken) {
|
|
2824
|
+
if (!this.refreshPromise) {
|
|
2825
|
+
this.refreshPromise = this.refreshTokens();
|
|
2826
|
+
}
|
|
2827
|
+
return this.refreshPromise;
|
|
2828
|
+
}
|
|
2829
|
+
return this.tokens.accessToken;
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Refresh access token
|
|
2833
|
+
*/
|
|
2834
|
+
async refreshTokens() {
|
|
2835
|
+
if (!this.tokens?.refreshToken) {
|
|
2836
|
+
throw new Error("No refresh token available");
|
|
2837
|
+
}
|
|
2838
|
+
try {
|
|
2839
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
2840
|
+
method: "POST",
|
|
2841
|
+
headers: {
|
|
2842
|
+
"Content-Type": "application/json"
|
|
2843
|
+
},
|
|
2844
|
+
body: JSON.stringify({
|
|
2845
|
+
grant_type: "refresh_token",
|
|
2846
|
+
refresh_token: this.tokens.refreshToken,
|
|
2847
|
+
client_id: this.config.clientId
|
|
2848
|
+
})
|
|
2849
|
+
});
|
|
2850
|
+
if (!response.ok) {
|
|
2851
|
+
this.clearAuth();
|
|
2852
|
+
throw new Error("Session expired. Please sign in again.");
|
|
2853
|
+
}
|
|
2854
|
+
const data = await response.json();
|
|
2855
|
+
this.tokens = {
|
|
2856
|
+
accessToken: data.access_token,
|
|
2857
|
+
refreshToken: data.refresh_token || this.tokens.refreshToken,
|
|
2858
|
+
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
2859
|
+
scopes: data.scope?.split(" ") || this.tokens.scopes,
|
|
2860
|
+
instanceId: data.instance_id || this.tokens.instanceId
|
|
2861
|
+
};
|
|
2862
|
+
this.saveTokens();
|
|
2863
|
+
return this.tokens.accessToken;
|
|
2864
|
+
} finally {
|
|
2865
|
+
this.refreshPromise = null;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
2868
|
+
/**
|
|
2869
|
+
* Get user info from MindCache
|
|
2870
|
+
*/
|
|
2871
|
+
async getUserInfo() {
|
|
2872
|
+
const token = await this.getAccessToken();
|
|
2873
|
+
const response = await fetch(DEFAULT_USERINFO_URL, {
|
|
2874
|
+
headers: {
|
|
2875
|
+
Authorization: `Bearer ${token}`
|
|
2876
|
+
}
|
|
2877
|
+
});
|
|
2878
|
+
if (!response.ok) {
|
|
2879
|
+
throw new Error("Failed to get user info");
|
|
2880
|
+
}
|
|
2881
|
+
const data = await response.json();
|
|
2882
|
+
return {
|
|
2883
|
+
id: data.sub,
|
|
2884
|
+
email: data.email,
|
|
2885
|
+
name: data.name,
|
|
2886
|
+
instanceId: data.instance_id
|
|
2887
|
+
};
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Logout - revoke tokens and clear storage
|
|
2891
|
+
*/
|
|
2892
|
+
async logout() {
|
|
2893
|
+
if (this.tokens?.accessToken) {
|
|
2894
|
+
try {
|
|
2895
|
+
await fetch(this.config.tokenUrl.replace("/token", "/revoke"), {
|
|
2896
|
+
method: "POST",
|
|
2897
|
+
headers: {
|
|
2898
|
+
"Content-Type": "application/json"
|
|
2899
|
+
},
|
|
2900
|
+
body: JSON.stringify({
|
|
2901
|
+
token: this.tokens.accessToken
|
|
2902
|
+
})
|
|
2903
|
+
});
|
|
2904
|
+
} catch {
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
this.clearAuth();
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* Clear authentication state
|
|
2911
|
+
*/
|
|
2912
|
+
clearAuth() {
|
|
2913
|
+
this.tokens = null;
|
|
2914
|
+
this.removeStorage("tokens");
|
|
2915
|
+
}
|
|
2916
|
+
/**
|
|
2917
|
+
* Token provider function for MindCache cloud config
|
|
2918
|
+
* Use this with MindCacheCloudOptions.tokenProvider
|
|
2919
|
+
*/
|
|
2920
|
+
tokenProvider = async () => {
|
|
2921
|
+
return this.getAccessToken();
|
|
2922
|
+
};
|
|
2923
|
+
// Storage helpers
|
|
2924
|
+
getStorage(key) {
|
|
2925
|
+
if (typeof localStorage === "undefined") {
|
|
2926
|
+
return null;
|
|
2927
|
+
}
|
|
2928
|
+
return localStorage.getItem(`${this.config.storagePrefix}_${key}`);
|
|
2929
|
+
}
|
|
2930
|
+
setStorage(key, value) {
|
|
2931
|
+
if (typeof localStorage === "undefined") {
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
localStorage.setItem(`${this.config.storagePrefix}_${key}`, value);
|
|
2935
|
+
}
|
|
2936
|
+
removeStorage(key) {
|
|
2937
|
+
if (typeof localStorage === "undefined") {
|
|
2938
|
+
return;
|
|
2939
|
+
}
|
|
2940
|
+
localStorage.removeItem(`${this.config.storagePrefix}_${key}`);
|
|
2941
|
+
}
|
|
2942
|
+
clearStorage() {
|
|
2943
|
+
this.removeStorage("state");
|
|
2944
|
+
this.removeStorage("code_verifier");
|
|
2945
|
+
this.removeStorage("tokens");
|
|
2946
|
+
}
|
|
2947
|
+
loadTokens() {
|
|
2948
|
+
const stored = this.getStorage("tokens");
|
|
2949
|
+
if (stored) {
|
|
2950
|
+
try {
|
|
2951
|
+
this.tokens = JSON.parse(stored);
|
|
2952
|
+
} catch {
|
|
2953
|
+
this.tokens = null;
|
|
2954
|
+
}
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
saveTokens() {
|
|
2958
|
+
if (this.tokens) {
|
|
2959
|
+
this.setStorage("tokens", JSON.stringify(this.tokens));
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
};
|
|
2963
|
+
function createOAuthClient(config) {
|
|
2964
|
+
return new OAuthClient(config);
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2274
2967
|
// src/local/index.ts
|
|
2275
2968
|
init_IndexedDBAdapter();
|
|
2276
2969
|
function useMindCache(options) {
|
|
@@ -2313,7 +3006,9 @@ var mindcache = new MindCache();
|
|
|
2313
3006
|
|
|
2314
3007
|
exports.DEFAULT_KEY_ATTRIBUTES = DEFAULT_KEY_ATTRIBUTES;
|
|
2315
3008
|
exports.MindCache = MindCache;
|
|
3009
|
+
exports.OAuthClient = OAuthClient;
|
|
2316
3010
|
exports.SystemTagHelpers = SystemTagHelpers;
|
|
3011
|
+
exports.createOAuthClient = createOAuthClient;
|
|
2317
3012
|
exports.mindcache = mindcache;
|
|
2318
3013
|
exports.useMindCache = useMindCache;
|
|
2319
3014
|
//# sourceMappingURL=index.js.map
|