@agenticmail/core 0.9.23 → 0.9.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-J6JINNJ3.js +724 -0
- package/dist/index.cjs +1173 -663
- package/dist/index.d.cts +366 -1
- package/dist/index.d.ts +366 -1
- package/dist/index.js +161 -395
- package/dist/skills/built-in/airline-change-or-refund.json +111 -0
- package/dist/skills/built-in/book-medical-appointment.json +99 -0
- package/dist/skills/built-in/book-restaurant-reservation.json +93 -0
- package/dist/skills/built-in/cancel-subscription-graceful.json +101 -0
- package/dist/skills/built-in/court-administrative-checkin.json +99 -0
- package/dist/skills/built-in/dispute-credit-card-charge.json +105 -0
- package/dist/skills/built-in/handle-debt-collector.json +109 -0
- package/dist/skills/built-in/negotiate-bill-reduction.json +116 -0
- package/dist/skills/built-in/schedule-home-service.json +100 -0
- package/dist/skills-RE3S767B.js +23 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -705,6 +705,746 @@ export default {
|
|
|
705
705
|
}
|
|
706
706
|
});
|
|
707
707
|
|
|
708
|
+
// src/memory/text-search.ts
|
|
709
|
+
function stem(word) {
|
|
710
|
+
if (word.length < 3) return word;
|
|
711
|
+
let stemmed = word;
|
|
712
|
+
for (const [pattern, replacement, minLen] of STEM_RULES) {
|
|
713
|
+
if (stemmed.length >= minLen && pattern.test(stemmed)) {
|
|
714
|
+
stemmed = stemmed.replace(pattern, replacement);
|
|
715
|
+
break;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (stemmed.length > 2 && DOUBLE_CONSONANT.test(stemmed)) {
|
|
719
|
+
stemmed = stemmed.slice(0, -1);
|
|
720
|
+
}
|
|
721
|
+
return stemmed;
|
|
722
|
+
}
|
|
723
|
+
function tokenize(text) {
|
|
724
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t)).map(stem);
|
|
725
|
+
}
|
|
726
|
+
var BM25_K1, BM25_B, FIELD_WEIGHT_TITLE, FIELD_WEIGHT_TAGS, FIELD_WEIGHT_CONTENT, PREFIX_MATCH_PENALTY, STOP_WORDS, STEM_RULES, DOUBLE_CONSONANT, MemorySearchIndex;
|
|
727
|
+
var init_text_search = __esm({
|
|
728
|
+
"src/memory/text-search.ts"() {
|
|
729
|
+
"use strict";
|
|
730
|
+
BM25_K1 = 1.2;
|
|
731
|
+
BM25_B = 0.75;
|
|
732
|
+
FIELD_WEIGHT_TITLE = 3;
|
|
733
|
+
FIELD_WEIGHT_TAGS = 2;
|
|
734
|
+
FIELD_WEIGHT_CONTENT = 1;
|
|
735
|
+
PREFIX_MATCH_PENALTY = 0.7;
|
|
736
|
+
STOP_WORDS = /* @__PURE__ */ new Set([
|
|
737
|
+
"a",
|
|
738
|
+
"about",
|
|
739
|
+
"above",
|
|
740
|
+
"after",
|
|
741
|
+
"again",
|
|
742
|
+
"against",
|
|
743
|
+
"all",
|
|
744
|
+
"am",
|
|
745
|
+
"an",
|
|
746
|
+
"and",
|
|
747
|
+
"any",
|
|
748
|
+
"are",
|
|
749
|
+
"as",
|
|
750
|
+
"at",
|
|
751
|
+
"be",
|
|
752
|
+
"because",
|
|
753
|
+
"been",
|
|
754
|
+
"before",
|
|
755
|
+
"being",
|
|
756
|
+
"below",
|
|
757
|
+
"between",
|
|
758
|
+
"both",
|
|
759
|
+
"but",
|
|
760
|
+
"by",
|
|
761
|
+
"can",
|
|
762
|
+
"could",
|
|
763
|
+
"did",
|
|
764
|
+
"do",
|
|
765
|
+
"does",
|
|
766
|
+
"doing",
|
|
767
|
+
"down",
|
|
768
|
+
"during",
|
|
769
|
+
"each",
|
|
770
|
+
"either",
|
|
771
|
+
"every",
|
|
772
|
+
"few",
|
|
773
|
+
"for",
|
|
774
|
+
"from",
|
|
775
|
+
"further",
|
|
776
|
+
"get",
|
|
777
|
+
"got",
|
|
778
|
+
"had",
|
|
779
|
+
"has",
|
|
780
|
+
"have",
|
|
781
|
+
"having",
|
|
782
|
+
"he",
|
|
783
|
+
"her",
|
|
784
|
+
"here",
|
|
785
|
+
"hers",
|
|
786
|
+
"herself",
|
|
787
|
+
"him",
|
|
788
|
+
"himself",
|
|
789
|
+
"his",
|
|
790
|
+
"how",
|
|
791
|
+
"i",
|
|
792
|
+
"if",
|
|
793
|
+
"in",
|
|
794
|
+
"into",
|
|
795
|
+
"is",
|
|
796
|
+
"it",
|
|
797
|
+
"its",
|
|
798
|
+
"itself",
|
|
799
|
+
"just",
|
|
800
|
+
"may",
|
|
801
|
+
"me",
|
|
802
|
+
"might",
|
|
803
|
+
"more",
|
|
804
|
+
"most",
|
|
805
|
+
"must",
|
|
806
|
+
"my",
|
|
807
|
+
"myself",
|
|
808
|
+
"neither",
|
|
809
|
+
"no",
|
|
810
|
+
"nor",
|
|
811
|
+
"not",
|
|
812
|
+
"now",
|
|
813
|
+
"of",
|
|
814
|
+
"off",
|
|
815
|
+
"on",
|
|
816
|
+
"once",
|
|
817
|
+
"only",
|
|
818
|
+
"or",
|
|
819
|
+
"other",
|
|
820
|
+
"ought",
|
|
821
|
+
"our",
|
|
822
|
+
"ours",
|
|
823
|
+
"ourselves",
|
|
824
|
+
"out",
|
|
825
|
+
"over",
|
|
826
|
+
"own",
|
|
827
|
+
"same",
|
|
828
|
+
"shall",
|
|
829
|
+
"she",
|
|
830
|
+
"should",
|
|
831
|
+
"so",
|
|
832
|
+
"some",
|
|
833
|
+
"such",
|
|
834
|
+
"than",
|
|
835
|
+
"that",
|
|
836
|
+
"the",
|
|
837
|
+
"their",
|
|
838
|
+
"theirs",
|
|
839
|
+
"them",
|
|
840
|
+
"themselves",
|
|
841
|
+
"then",
|
|
842
|
+
"there",
|
|
843
|
+
"these",
|
|
844
|
+
"they",
|
|
845
|
+
"this",
|
|
846
|
+
"those",
|
|
847
|
+
"through",
|
|
848
|
+
"to",
|
|
849
|
+
"too",
|
|
850
|
+
"under",
|
|
851
|
+
"until",
|
|
852
|
+
"up",
|
|
853
|
+
"us",
|
|
854
|
+
"very",
|
|
855
|
+
"was",
|
|
856
|
+
"we",
|
|
857
|
+
"were",
|
|
858
|
+
"what",
|
|
859
|
+
"when",
|
|
860
|
+
"where",
|
|
861
|
+
"which",
|
|
862
|
+
"while",
|
|
863
|
+
"who",
|
|
864
|
+
"whom",
|
|
865
|
+
"why",
|
|
866
|
+
"will",
|
|
867
|
+
"with",
|
|
868
|
+
"would",
|
|
869
|
+
"yet",
|
|
870
|
+
"you",
|
|
871
|
+
"your",
|
|
872
|
+
"yours",
|
|
873
|
+
"yourself",
|
|
874
|
+
"yourselves"
|
|
875
|
+
]);
|
|
876
|
+
STEM_RULES = [
|
|
877
|
+
// Step 1: plurals and past participles
|
|
878
|
+
[/ies$/, "i", 3],
|
|
879
|
+
// policies → polici,eries → eri
|
|
880
|
+
[/sses$/, "ss", 4],
|
|
881
|
+
// addresses → address
|
|
882
|
+
[/([^s])s$/, "$1", 3],
|
|
883
|
+
// items → item, but not "ss"
|
|
884
|
+
[/eed$/, "ee", 4],
|
|
885
|
+
// agreed → agree
|
|
886
|
+
[/ed$/, "", 3],
|
|
887
|
+
// configured → configur, but min length 3
|
|
888
|
+
[/ing$/, "", 4],
|
|
889
|
+
// running → runn → run (handled below)
|
|
890
|
+
// Step 2: derivational suffixes
|
|
891
|
+
[/ational$/, "ate", 6],
|
|
892
|
+
// relational → relate
|
|
893
|
+
[/tion$/, "t", 5],
|
|
894
|
+
// adoption → adopt
|
|
895
|
+
[/ness$/, "", 5],
|
|
896
|
+
// awareness → aware
|
|
897
|
+
[/ment$/, "", 5],
|
|
898
|
+
// deployment → deploy
|
|
899
|
+
[/able$/, "", 5],
|
|
900
|
+
// configurable → configur
|
|
901
|
+
[/ible$/, "", 5],
|
|
902
|
+
// accessible → access
|
|
903
|
+
[/ful$/, "", 5],
|
|
904
|
+
// powerful → power
|
|
905
|
+
[/ous$/, "", 5],
|
|
906
|
+
// dangerous → danger
|
|
907
|
+
[/ive$/, "", 5],
|
|
908
|
+
// interactive → interact
|
|
909
|
+
[/ize$/, "", 4],
|
|
910
|
+
// normalize → normal
|
|
911
|
+
[/ise$/, "", 4],
|
|
912
|
+
// organise → organ
|
|
913
|
+
[/ally$/, "", 5],
|
|
914
|
+
// automatically → automat
|
|
915
|
+
[/ly$/, "", 4],
|
|
916
|
+
// quickly → quick
|
|
917
|
+
[/er$/, "", 4]
|
|
918
|
+
// handler → handl
|
|
919
|
+
];
|
|
920
|
+
DOUBLE_CONSONANT = /([^aeiou])\1$/;
|
|
921
|
+
MemorySearchIndex = class {
|
|
922
|
+
/** Posting lists: stemmed term → Set of memory IDs containing it */
|
|
923
|
+
postings = /* @__PURE__ */ new Map();
|
|
924
|
+
/** Per-document metadata for BM25 scoring */
|
|
925
|
+
docs = /* @__PURE__ */ new Map();
|
|
926
|
+
/** Pre-computed IDF values. Stale flag triggers lazy recomputation. */
|
|
927
|
+
idf = /* @__PURE__ */ new Map();
|
|
928
|
+
idfStale = true;
|
|
929
|
+
/** 3-character prefix map for prefix matching: prefix → Set of full stems */
|
|
930
|
+
prefixMap = /* @__PURE__ */ new Map();
|
|
931
|
+
/** Total weighted document length (for computing average) */
|
|
932
|
+
totalWeightedLen = 0;
|
|
933
|
+
get docCount() {
|
|
934
|
+
return this.docs.size;
|
|
935
|
+
}
|
|
936
|
+
get avgDocLen() {
|
|
937
|
+
return this.docs.size > 0 ? this.totalWeightedLen / this.docs.size : 1;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Index a memory entry. Extracts stems from title, content, and tags
|
|
941
|
+
* with field-specific weighting and builds posting lists.
|
|
942
|
+
*/
|
|
943
|
+
addDocument(id, entry) {
|
|
944
|
+
if (this.docs.has(id)) this.removeDocument(id);
|
|
945
|
+
const titleTokens = tokenize(entry.title);
|
|
946
|
+
const contentTokens = tokenize(entry.content);
|
|
947
|
+
const tagTokens = entry.tags.flatMap((t) => tokenize(t));
|
|
948
|
+
const weightedTf = /* @__PURE__ */ new Map();
|
|
949
|
+
for (const t of titleTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_TITLE);
|
|
950
|
+
for (const t of tagTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_TAGS);
|
|
951
|
+
for (const t of contentTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_CONTENT);
|
|
952
|
+
const weightedLen = titleTokens.length * FIELD_WEIGHT_TITLE + tagTokens.length * FIELD_WEIGHT_TAGS + contentTokens.length * FIELD_WEIGHT_CONTENT;
|
|
953
|
+
const allStems = /* @__PURE__ */ new Set();
|
|
954
|
+
for (const t of weightedTf.keys()) allStems.add(t);
|
|
955
|
+
const stemSequence = [...titleTokens, ...contentTokens];
|
|
956
|
+
const docRecord = { weightedTf, weightedLen, allStems, stemSequence };
|
|
957
|
+
this.docs.set(id, docRecord);
|
|
958
|
+
this.totalWeightedLen += weightedLen;
|
|
959
|
+
for (const term of allStems) {
|
|
960
|
+
let posting = this.postings.get(term);
|
|
961
|
+
if (!posting) {
|
|
962
|
+
posting = /* @__PURE__ */ new Set();
|
|
963
|
+
this.postings.set(term, posting);
|
|
964
|
+
}
|
|
965
|
+
posting.add(id);
|
|
966
|
+
if (term.length >= 3) {
|
|
967
|
+
const prefix = term.slice(0, 3);
|
|
968
|
+
let prefixSet = this.prefixMap.get(prefix);
|
|
969
|
+
if (!prefixSet) {
|
|
970
|
+
prefixSet = /* @__PURE__ */ new Set();
|
|
971
|
+
this.prefixMap.set(prefix, prefixSet);
|
|
972
|
+
}
|
|
973
|
+
prefixSet.add(term);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
this.idfStale = true;
|
|
977
|
+
}
|
|
978
|
+
/** Remove a document from the index. */
|
|
979
|
+
removeDocument(id) {
|
|
980
|
+
const doc = this.docs.get(id);
|
|
981
|
+
if (!doc) return;
|
|
982
|
+
this.totalWeightedLen -= doc.weightedLen;
|
|
983
|
+
this.docs.delete(id);
|
|
984
|
+
for (const term of doc.allStems) {
|
|
985
|
+
const posting = this.postings.get(term);
|
|
986
|
+
if (posting) {
|
|
987
|
+
posting.delete(id);
|
|
988
|
+
if (posting.size === 0) {
|
|
989
|
+
this.postings.delete(term);
|
|
990
|
+
if (term.length >= 3) {
|
|
991
|
+
const prefixSet = this.prefixMap.get(term.slice(0, 3));
|
|
992
|
+
if (prefixSet) {
|
|
993
|
+
prefixSet.delete(term);
|
|
994
|
+
if (prefixSet.size === 0) this.prefixMap.delete(term.slice(0, 3));
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
this.idfStale = true;
|
|
1001
|
+
}
|
|
1002
|
+
/** Recompute IDF values for all terms. Called lazily before search. */
|
|
1003
|
+
refreshIdf() {
|
|
1004
|
+
if (!this.idfStale) return;
|
|
1005
|
+
const N = this.docs.size;
|
|
1006
|
+
this.idf.clear();
|
|
1007
|
+
for (const [term, posting] of this.postings) {
|
|
1008
|
+
const df = posting.size;
|
|
1009
|
+
this.idf.set(term, Math.log((N - df + 0.5) / (df + 0.5) + 1));
|
|
1010
|
+
}
|
|
1011
|
+
this.idfStale = false;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Expand query terms with prefix matches.
|
|
1015
|
+
* "deploy" → ["deploy", "deployment", "deploying", ...] (if they exist in the index)
|
|
1016
|
+
*/
|
|
1017
|
+
expandQueryTerms(queryStems) {
|
|
1018
|
+
const expanded = /* @__PURE__ */ new Map();
|
|
1019
|
+
for (const qs of queryStems) {
|
|
1020
|
+
if (this.postings.has(qs)) {
|
|
1021
|
+
expanded.set(qs, Math.max(expanded.get(qs) || 0, 1));
|
|
1022
|
+
}
|
|
1023
|
+
if (qs.length >= 3) {
|
|
1024
|
+
const prefix = qs.slice(0, 3);
|
|
1025
|
+
const candidates = this.prefixMap.get(prefix);
|
|
1026
|
+
if (candidates) {
|
|
1027
|
+
for (const candidate of candidates) {
|
|
1028
|
+
if (candidate !== qs && candidate.startsWith(qs)) {
|
|
1029
|
+
expanded.set(candidate, Math.max(expanded.get(candidate) || 0, PREFIX_MATCH_PENALTY));
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return expanded;
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Compute bigram proximity boost: if two query terms appear adjacent
|
|
1039
|
+
* in the document's stem sequence, boost the score.
|
|
1040
|
+
*/
|
|
1041
|
+
bigramProximityBoost(docId, queryStems) {
|
|
1042
|
+
if (queryStems.length < 2) return 0;
|
|
1043
|
+
const doc = this.docs.get(docId);
|
|
1044
|
+
if (!doc || doc.stemSequence.length < 2) return 0;
|
|
1045
|
+
let boost = 0;
|
|
1046
|
+
const seq = doc.stemSequence;
|
|
1047
|
+
const querySet = new Set(queryStems);
|
|
1048
|
+
for (let i = 0; i < seq.length - 1; i++) {
|
|
1049
|
+
if (querySet.has(seq[i]) && querySet.has(seq[i + 1]) && seq[i] !== seq[i + 1]) {
|
|
1050
|
+
boost += 0.5;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return Math.min(boost, 2);
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Search the index for documents matching a query.
|
|
1057
|
+
* Returns scored results sorted by BM25F relevance.
|
|
1058
|
+
*
|
|
1059
|
+
* @param query - Raw query string
|
|
1060
|
+
* @param candidateIds - Optional: only score these document IDs (for agent-scoped search)
|
|
1061
|
+
* @returns Array of { id, score } sorted by descending score
|
|
1062
|
+
*/
|
|
1063
|
+
search(query, candidateIds) {
|
|
1064
|
+
const queryStems = tokenize(query);
|
|
1065
|
+
if (queryStems.length === 0) return [];
|
|
1066
|
+
this.refreshIdf();
|
|
1067
|
+
const expandedTerms = this.expandQueryTerms(queryStems);
|
|
1068
|
+
if (expandedTerms.size === 0) return [];
|
|
1069
|
+
const avgDl = this.avgDocLen;
|
|
1070
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
1071
|
+
for (const term of expandedTerms.keys()) {
|
|
1072
|
+
const posting = this.postings.get(term);
|
|
1073
|
+
if (posting) {
|
|
1074
|
+
for (const docId of posting) {
|
|
1075
|
+
if (!candidateIds || candidateIds.has(docId)) candidates.add(docId);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
const results = [];
|
|
1080
|
+
for (const docId of candidates) {
|
|
1081
|
+
const doc = this.docs.get(docId);
|
|
1082
|
+
if (!doc) continue;
|
|
1083
|
+
let score = 0;
|
|
1084
|
+
for (const [term, weight] of expandedTerms) {
|
|
1085
|
+
const tf = doc.weightedTf.get(term) || 0;
|
|
1086
|
+
if (tf === 0) continue;
|
|
1087
|
+
const termIdf = this.idf.get(term) || 0;
|
|
1088
|
+
const numerator = tf * (BM25_K1 + 1);
|
|
1089
|
+
const denominator = tf + BM25_K1 * (1 - BM25_B + BM25_B * (doc.weightedLen / avgDl));
|
|
1090
|
+
score += termIdf * (numerator / denominator) * weight;
|
|
1091
|
+
}
|
|
1092
|
+
score += this.bigramProximityBoost(docId, queryStems);
|
|
1093
|
+
if (score > 0) results.push({ id: docId, score });
|
|
1094
|
+
}
|
|
1095
|
+
results.sort((a, b) => b.score - a.score);
|
|
1096
|
+
return results;
|
|
1097
|
+
}
|
|
1098
|
+
/** Check if a document exists in the index. */
|
|
1099
|
+
has(id) {
|
|
1100
|
+
return this.docs.has(id);
|
|
1101
|
+
}
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
// src/skills/registry.ts
|
|
1107
|
+
function builtInDir() {
|
|
1108
|
+
const here = (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta2.url));
|
|
1109
|
+
return (0, import_node_path5.join)(here, "built-in");
|
|
1110
|
+
}
|
|
1111
|
+
function userDir() {
|
|
1112
|
+
const dir2 = (0, import_node_path5.join)((0, import_node_os4.homedir)(), ".agenticmail", "skills");
|
|
1113
|
+
try {
|
|
1114
|
+
if (!(0, import_node_fs4.existsSync)(dir2)) (0, import_node_fs4.mkdirSync)(dir2, { recursive: true });
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
return dir2;
|
|
1118
|
+
}
|
|
1119
|
+
function skillToIndexDoc(s) {
|
|
1120
|
+
const contentParts = [
|
|
1121
|
+
s.description,
|
|
1122
|
+
s.context?.when_to_use ?? "",
|
|
1123
|
+
...s.principles ?? [],
|
|
1124
|
+
...Object.values(s.phrases ?? {}),
|
|
1125
|
+
...(s.tactics ?? []).flatMap((t) => [t.name, t.when, t.script]),
|
|
1126
|
+
...s.success_signals ?? [],
|
|
1127
|
+
...s.failure_signals ?? []
|
|
1128
|
+
];
|
|
1129
|
+
return {
|
|
1130
|
+
title: s.name,
|
|
1131
|
+
// Include category as a tag for free — a query of "negotiation"
|
|
1132
|
+
// hits both literal `negotiation`-category skills AND skills
|
|
1133
|
+
// that tagged themselves "negotiation".
|
|
1134
|
+
tags: [...s.tags ?? [], s.category],
|
|
1135
|
+
content: contentParts.filter(Boolean).join(" ")
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
function loadAllSkillsFromDisk() {
|
|
1139
|
+
const all = /* @__PURE__ */ new Map();
|
|
1140
|
+
const dirs = [builtInDir(), userDir()];
|
|
1141
|
+
for (const dir2 of dirs) {
|
|
1142
|
+
if (!(0, import_node_fs4.existsSync)(dir2)) continue;
|
|
1143
|
+
let entries;
|
|
1144
|
+
try {
|
|
1145
|
+
entries = (0, import_node_fs4.readdirSync)(dir2);
|
|
1146
|
+
} catch {
|
|
1147
|
+
continue;
|
|
1148
|
+
}
|
|
1149
|
+
for (const entry of entries) {
|
|
1150
|
+
if (!entry.endsWith(".json")) continue;
|
|
1151
|
+
const fullPath = (0, import_node_path5.join)(dir2, entry);
|
|
1152
|
+
try {
|
|
1153
|
+
const st = (0, import_node_fs4.statSync)(fullPath);
|
|
1154
|
+
if (!st.isFile()) continue;
|
|
1155
|
+
const raw = (0, import_node_fs4.readFileSync)(fullPath, "utf-8");
|
|
1156
|
+
const parsed = JSON.parse(raw);
|
|
1157
|
+
const errors = validateSkill(parsed);
|
|
1158
|
+
if (errors.length > 0) {
|
|
1159
|
+
console.warn(`[skills] ${entry} invalid, skipping: ${errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
1160
|
+
continue;
|
|
1161
|
+
}
|
|
1162
|
+
all.set(parsed.id, parsed);
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
console.warn(`[skills] could not load ${fullPath}: ${err.message}`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
return all;
|
|
1169
|
+
}
|
|
1170
|
+
function ensureLoaded() {
|
|
1171
|
+
const now = Date.now();
|
|
1172
|
+
if (cache.byId && cache.index && now - cache.ts < CACHE_TTL_MS) {
|
|
1173
|
+
return { byId: cache.byId, index: cache.index };
|
|
1174
|
+
}
|
|
1175
|
+
const fresh = loadAllSkillsFromDisk();
|
|
1176
|
+
const index = new MemorySearchIndex();
|
|
1177
|
+
for (const [id, skill] of fresh) {
|
|
1178
|
+
try {
|
|
1179
|
+
index.addDocument(id, skillToIndexDoc(skill));
|
|
1180
|
+
} catch {
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
cache.byId = fresh;
|
|
1184
|
+
cache.index = index;
|
|
1185
|
+
cache.ts = now;
|
|
1186
|
+
return { byId: fresh, index };
|
|
1187
|
+
}
|
|
1188
|
+
function invalidateSkillCache() {
|
|
1189
|
+
cache.byId = null;
|
|
1190
|
+
cache.index = null;
|
|
1191
|
+
cache.ts = 0;
|
|
1192
|
+
}
|
|
1193
|
+
function validateSkill(s) {
|
|
1194
|
+
const errs = [];
|
|
1195
|
+
if (!s || typeof s !== "object" || Array.isArray(s)) {
|
|
1196
|
+
return [{ path: "$", message: "skill must be a JSON object" }];
|
|
1197
|
+
}
|
|
1198
|
+
const sk = s;
|
|
1199
|
+
const requireString = (key, minLen = 1) => {
|
|
1200
|
+
const v = sk[key];
|
|
1201
|
+
if (typeof v !== "string" || v.length < minLen) {
|
|
1202
|
+
errs.push({ path: key, message: `must be a non-empty string` });
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
const requireArray = (key, minLen = 1) => {
|
|
1206
|
+
const v = sk[key];
|
|
1207
|
+
if (!Array.isArray(v) || v.length < minLen) {
|
|
1208
|
+
errs.push({ path: key, message: `must be a non-empty array` });
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
requireString("id");
|
|
1212
|
+
if (typeof sk.id === "string" && !/^[a-z0-9]+(-[a-z0-9]+)*$/.test(sk.id)) {
|
|
1213
|
+
errs.push({ path: "id", message: "must be lowercase-hyphenated slug (a-z, 0-9, -)" });
|
|
1214
|
+
}
|
|
1215
|
+
requireString("name");
|
|
1216
|
+
requireString("version");
|
|
1217
|
+
requireString("description");
|
|
1218
|
+
requireString("category");
|
|
1219
|
+
const validCategories = [
|
|
1220
|
+
"negotiation",
|
|
1221
|
+
"customer-service",
|
|
1222
|
+
"reservations",
|
|
1223
|
+
"medical-admin",
|
|
1224
|
+
"legal-admin",
|
|
1225
|
+
"finance-admin",
|
|
1226
|
+
"real-estate",
|
|
1227
|
+
"travel",
|
|
1228
|
+
"subscription",
|
|
1229
|
+
"home-services",
|
|
1230
|
+
"social",
|
|
1231
|
+
"civic",
|
|
1232
|
+
"employment",
|
|
1233
|
+
"debt-collection",
|
|
1234
|
+
"other"
|
|
1235
|
+
];
|
|
1236
|
+
if (typeof sk.category === "string" && !validCategories.includes(sk.category)) {
|
|
1237
|
+
errs.push({ path: "category", message: `unknown category "${sk.category}"; must be one of: ${validCategories.join(", ")}` });
|
|
1238
|
+
}
|
|
1239
|
+
if (!Array.isArray(sk.tags)) errs.push({ path: "tags", message: "must be an array of strings" });
|
|
1240
|
+
if (sk.disclaimer !== null && typeof sk.disclaimer !== "string") {
|
|
1241
|
+
errs.push({ path: "disclaimer", message: "must be a string or null" });
|
|
1242
|
+
}
|
|
1243
|
+
if (!sk.context || typeof sk.context !== "object") {
|
|
1244
|
+
errs.push({ path: "context", message: "must be an object" });
|
|
1245
|
+
} else {
|
|
1246
|
+
const ctx = sk.context;
|
|
1247
|
+
if (typeof ctx.when_to_use !== "string") errs.push({ path: "context.when_to_use", message: "must be a string" });
|
|
1248
|
+
if (!Array.isArray(ctx.preconditions)) errs.push({ path: "context.preconditions", message: "must be an array" });
|
|
1249
|
+
if (typeof ctx.estimated_call_duration_minutes !== "number") errs.push({ path: "context.estimated_call_duration_minutes", message: "must be a number" });
|
|
1250
|
+
}
|
|
1251
|
+
requireArray("principles", 2);
|
|
1252
|
+
if (!sk.phrases || typeof sk.phrases !== "object") errs.push({ path: "phrases", message: "must be an object of {key: phrase}" });
|
|
1253
|
+
if (!Array.isArray(sk.tactics) || sk.tactics.length === 0) {
|
|
1254
|
+
errs.push({ path: "tactics", message: "must be a non-empty array" });
|
|
1255
|
+
} else {
|
|
1256
|
+
sk.tactics.forEach((t, i) => {
|
|
1257
|
+
if (!t || typeof t !== "object") {
|
|
1258
|
+
errs.push({ path: `tactics[${i}]`, message: "must be an object" });
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
const tactic = t;
|
|
1262
|
+
if (typeof tactic.name !== "string") errs.push({ path: `tactics[${i}].name`, message: "must be a string" });
|
|
1263
|
+
if (typeof tactic.when !== "string") errs.push({ path: `tactics[${i}].when`, message: "must be a string" });
|
|
1264
|
+
if (typeof tactic.script !== "string" || tactic.script.length < 5) {
|
|
1265
|
+
errs.push({ path: `tactics[${i}].script`, message: "must be a non-empty string (>= 5 chars)" });
|
|
1266
|
+
}
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
requireArray("boundaries", 1);
|
|
1270
|
+
if (!Array.isArray(sk.success_signals)) errs.push({ path: "success_signals", message: "must be an array" });
|
|
1271
|
+
if (!Array.isArray(sk.failure_signals)) errs.push({ path: "failure_signals", message: "must be an array" });
|
|
1272
|
+
if (!sk.exit_strategy || typeof sk.exit_strategy !== "object") {
|
|
1273
|
+
errs.push({ path: "exit_strategy", message: "must be an object" });
|
|
1274
|
+
} else {
|
|
1275
|
+
const xs = sk.exit_strategy;
|
|
1276
|
+
if (typeof xs.on_success !== "string") errs.push({ path: "exit_strategy.on_success", message: "must be a string" });
|
|
1277
|
+
if (typeof xs.on_failure !== "string") errs.push({ path: "exit_strategy.on_failure", message: "must be a string" });
|
|
1278
|
+
}
|
|
1279
|
+
if (!Array.isArray(sk.required_user_info)) errs.push({ path: "required_user_info", message: "must be an array" });
|
|
1280
|
+
if (typeof sk.contributed_by !== "string") errs.push({ path: "contributed_by", message: "must be a string" });
|
|
1281
|
+
return errs;
|
|
1282
|
+
}
|
|
1283
|
+
function summarize(s) {
|
|
1284
|
+
return {
|
|
1285
|
+
id: s.id,
|
|
1286
|
+
name: s.name,
|
|
1287
|
+
category: s.category,
|
|
1288
|
+
tags: s.tags,
|
|
1289
|
+
description: s.description,
|
|
1290
|
+
version: s.version,
|
|
1291
|
+
disclaimer_required: !!s.disclaimer,
|
|
1292
|
+
estimated_call_duration_minutes: s.context.estimated_call_duration_minutes
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
function listSkills(opts = {}) {
|
|
1296
|
+
const all = Array.from(ensureLoaded().byId.values());
|
|
1297
|
+
const filtered = all.filter((s) => {
|
|
1298
|
+
if (opts.category && s.category !== opts.category) return false;
|
|
1299
|
+
if (opts.tag && !s.tags.includes(opts.tag.toLowerCase())) return false;
|
|
1300
|
+
return true;
|
|
1301
|
+
});
|
|
1302
|
+
return filtered.sort((a, b) => a.name.localeCompare(b.name)).map(summarize);
|
|
1303
|
+
}
|
|
1304
|
+
function searchSkills(query, limit = 20) {
|
|
1305
|
+
const q = query.trim();
|
|
1306
|
+
if (!q) return [];
|
|
1307
|
+
const { byId, index } = ensureLoaded();
|
|
1308
|
+
const ranked = index.search(q);
|
|
1309
|
+
if (ranked.length === 0) {
|
|
1310
|
+
const qLow = q.toLowerCase();
|
|
1311
|
+
const fallback = [];
|
|
1312
|
+
for (const s of byId.values()) {
|
|
1313
|
+
if (s.id.toLowerCase().includes(qLow) || s.name.toLowerCase().includes(qLow) || s.tags.some((t) => t.toLowerCase().includes(qLow))) {
|
|
1314
|
+
fallback.push(summarize(s));
|
|
1315
|
+
if (fallback.length >= limit) break;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return fallback;
|
|
1319
|
+
}
|
|
1320
|
+
const out = [];
|
|
1321
|
+
for (const { id } of ranked) {
|
|
1322
|
+
const skill = byId.get(id);
|
|
1323
|
+
if (!skill) continue;
|
|
1324
|
+
out.push(summarize(skill));
|
|
1325
|
+
if (out.length >= limit) break;
|
|
1326
|
+
}
|
|
1327
|
+
return out;
|
|
1328
|
+
}
|
|
1329
|
+
function loadSkill(id) {
|
|
1330
|
+
return ensureLoaded().byId.get(id) ?? null;
|
|
1331
|
+
}
|
|
1332
|
+
function saveUserSkill(skill) {
|
|
1333
|
+
const errors = validateSkill(skill);
|
|
1334
|
+
if (errors.length > 0) {
|
|
1335
|
+
throw new Error(`skill validation failed: ${errors.map((e) => `${e.path}: ${e.message}`).join("; ")}`);
|
|
1336
|
+
}
|
|
1337
|
+
const dir2 = userDir();
|
|
1338
|
+
const path2 = (0, import_node_path5.join)(dir2, `${skill.id}.json`);
|
|
1339
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1340
|
+
const out = {
|
|
1341
|
+
...skill,
|
|
1342
|
+
created_at: skill.created_at ?? now,
|
|
1343
|
+
updated_at: now
|
|
1344
|
+
};
|
|
1345
|
+
(0, import_node_fs4.writeFileSync)(path2, JSON.stringify(out, null, 2), "utf-8");
|
|
1346
|
+
invalidateSkillCache();
|
|
1347
|
+
return { path: path2 };
|
|
1348
|
+
}
|
|
1349
|
+
function skillFilename(id) {
|
|
1350
|
+
return `${(0, import_node_path5.basename)(id)}.json`;
|
|
1351
|
+
}
|
|
1352
|
+
function userSkillsDir() {
|
|
1353
|
+
return userDir();
|
|
1354
|
+
}
|
|
1355
|
+
var import_node_fs4, import_node_path5, import_node_url, import_node_os4, import_meta2, cache, CACHE_TTL_MS;
|
|
1356
|
+
var init_registry = __esm({
|
|
1357
|
+
"src/skills/registry.ts"() {
|
|
1358
|
+
"use strict";
|
|
1359
|
+
import_node_fs4 = require("fs");
|
|
1360
|
+
import_node_path5 = require("path");
|
|
1361
|
+
import_node_url = require("url");
|
|
1362
|
+
import_node_os4 = require("os");
|
|
1363
|
+
init_text_search();
|
|
1364
|
+
import_meta2 = {};
|
|
1365
|
+
cache = { ts: 0, byId: null, index: null };
|
|
1366
|
+
CACHE_TTL_MS = 5e3;
|
|
1367
|
+
}
|
|
1368
|
+
});
|
|
1369
|
+
|
|
1370
|
+
// src/skills/index.ts
|
|
1371
|
+
var skills_exports = {};
|
|
1372
|
+
__export(skills_exports, {
|
|
1373
|
+
invalidateSkillCache: () => invalidateSkillCache,
|
|
1374
|
+
listSkills: () => listSkills,
|
|
1375
|
+
loadSkill: () => loadSkill,
|
|
1376
|
+
renderSkillAsPrompt: () => renderSkillAsPrompt,
|
|
1377
|
+
saveUserSkill: () => saveUserSkill,
|
|
1378
|
+
searchSkills: () => searchSkills,
|
|
1379
|
+
skillFilename: () => skillFilename,
|
|
1380
|
+
userSkillsDir: () => userSkillsDir,
|
|
1381
|
+
validateSkill: () => validateSkill
|
|
1382
|
+
});
|
|
1383
|
+
function renderSkillAsPrompt(skill) {
|
|
1384
|
+
const lines = [];
|
|
1385
|
+
lines.push(`=== SKILL LOADED: ${skill.name} (v${skill.version}) ===`);
|
|
1386
|
+
lines.push(`Category: ${skill.category} Tags: ${skill.tags.join(", ")}`);
|
|
1387
|
+
lines.push("");
|
|
1388
|
+
lines.push(skill.description);
|
|
1389
|
+
lines.push("");
|
|
1390
|
+
if (skill.disclaimer) {
|
|
1391
|
+
lines.push("REQUIRED DISCLAIMER (recite at start of the substantive turn):");
|
|
1392
|
+
lines.push(` "${skill.disclaimer}"`);
|
|
1393
|
+
lines.push("");
|
|
1394
|
+
}
|
|
1395
|
+
lines.push("WHEN TO USE THIS:");
|
|
1396
|
+
lines.push(` ${skill.context.when_to_use}`);
|
|
1397
|
+
if (skill.context.preconditions.length > 0) {
|
|
1398
|
+
lines.push("Preconditions:");
|
|
1399
|
+
for (const p of skill.context.preconditions) lines.push(` - ${p}`);
|
|
1400
|
+
}
|
|
1401
|
+
lines.push("");
|
|
1402
|
+
lines.push("PRINCIPLES:");
|
|
1403
|
+
for (const p of skill.principles) lines.push(` - ${p}`);
|
|
1404
|
+
lines.push("");
|
|
1405
|
+
if (Object.keys(skill.phrases).length > 0) {
|
|
1406
|
+
lines.push("PHRASES (paraphrase to match your voice; keep the structural move):");
|
|
1407
|
+
for (const [k, v] of Object.entries(skill.phrases)) lines.push(` [${k}] "${v}"`);
|
|
1408
|
+
lines.push("");
|
|
1409
|
+
}
|
|
1410
|
+
if (skill.tactics.length > 0) {
|
|
1411
|
+
lines.push("TACTICS (try in order; fall back to next on failure):");
|
|
1412
|
+
skill.tactics.forEach((t, i) => {
|
|
1413
|
+
lines.push(` ${i + 1}. ${t.name}`);
|
|
1414
|
+
lines.push(` When: ${t.when}`);
|
|
1415
|
+
lines.push(` Script: "${t.script}"`);
|
|
1416
|
+
});
|
|
1417
|
+
lines.push("");
|
|
1418
|
+
}
|
|
1419
|
+
if (skill.boundaries.length > 0) {
|
|
1420
|
+
lines.push("HARD BOUNDARIES \u2014 do not cross:");
|
|
1421
|
+
for (const b of skill.boundaries) lines.push(` - ${b}`);
|
|
1422
|
+
lines.push("");
|
|
1423
|
+
}
|
|
1424
|
+
lines.push("SUCCESS SIGNALS:");
|
|
1425
|
+
for (const s of skill.success_signals) lines.push(` - ${s}`);
|
|
1426
|
+
lines.push("");
|
|
1427
|
+
lines.push("FAILURE SIGNALS \u2014 when these appear, escalate or exit:");
|
|
1428
|
+
for (const s of skill.failure_signals) lines.push(` - ${s}`);
|
|
1429
|
+
lines.push("");
|
|
1430
|
+
lines.push("EXIT:");
|
|
1431
|
+
lines.push(` On success: ${skill.exit_strategy.on_success}`);
|
|
1432
|
+
lines.push(` On failure: ${skill.exit_strategy.on_failure}`);
|
|
1433
|
+
if (skill.exit_strategy.follow_ups && skill.exit_strategy.follow_ups.length > 0) {
|
|
1434
|
+
lines.push(" Follow-ups (after the call):");
|
|
1435
|
+
for (const f of skill.exit_strategy.follow_ups) lines.push(` - ${f}`);
|
|
1436
|
+
}
|
|
1437
|
+
lines.push("");
|
|
1438
|
+
lines.push("=== END SKILL ===");
|
|
1439
|
+
return lines.join("\n");
|
|
1440
|
+
}
|
|
1441
|
+
var init_skills = __esm({
|
|
1442
|
+
"src/skills/index.ts"() {
|
|
1443
|
+
"use strict";
|
|
1444
|
+
init_registry();
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
|
|
708
1448
|
// src/index.ts
|
|
709
1449
|
var index_exports = {};
|
|
710
1450
|
__export(index_exports, {
|
|
@@ -736,6 +1476,7 @@ __export(index_exports, {
|
|
|
736
1476
|
GET_DATETIME_TOOL: () => GET_DATETIME_TOOL,
|
|
737
1477
|
GatewayManager: () => GatewayManager,
|
|
738
1478
|
InboxWatcher: () => InboxWatcher,
|
|
1479
|
+
LOAD_SKILL_TOOL: () => LOAD_SKILL_TOOL,
|
|
739
1480
|
MEMORY_CATEGORIES: () => MEMORY_CATEGORIES,
|
|
740
1481
|
MailReceiver: () => MailReceiver,
|
|
741
1482
|
MailSender: () => MailSender,
|
|
@@ -772,6 +1513,7 @@ __export(index_exports, {
|
|
|
772
1513
|
RelayBridge: () => RelayBridge,
|
|
773
1514
|
RelayGateway: () => RelayGateway,
|
|
774
1515
|
SEARCH_EMAIL_TOOL: () => SEARCH_EMAIL_TOOL,
|
|
1516
|
+
SEARCH_SKILLS_TOOL: () => SEARCH_SKILLS_TOOL,
|
|
775
1517
|
SPAM_THRESHOLD: () => SPAM_THRESHOLD,
|
|
776
1518
|
ServiceManager: () => ServiceManager,
|
|
777
1519
|
SetupManager: () => SetupManager,
|
|
@@ -851,6 +1593,7 @@ __export(index_exports, {
|
|
|
851
1593
|
getTelegramWebhookInfo: () => getTelegramWebhookInfo,
|
|
852
1594
|
hostSessionStoragePath: () => hostSessionStoragePath,
|
|
853
1595
|
inferPhoneRegion: () => inferPhoneRegion,
|
|
1596
|
+
invalidateSkillCache: () => invalidateSkillCache,
|
|
854
1597
|
isInternalEmail: () => isInternalEmail,
|
|
855
1598
|
isLoopbackMailHost: () => isLoopbackMailHost,
|
|
856
1599
|
isOperatorReplySender: () => isOperatorReplySender,
|
|
@@ -859,7 +1602,9 @@ __export(index_exports, {
|
|
|
859
1602
|
isTelegramChatAllowed: () => isTelegramChatAllowed,
|
|
860
1603
|
isTelegramStopCommand: () => isTelegramStopCommand,
|
|
861
1604
|
isValidPhoneNumber: () => isValidPhoneNumber,
|
|
1605
|
+
listSkills: () => listSkills,
|
|
862
1606
|
loadHostSession: () => loadHostSession,
|
|
1607
|
+
loadSkill: () => loadSkill,
|
|
863
1608
|
mapProviderSmsStatus: () => mapProviderSmsStatus,
|
|
864
1609
|
nextTelegramOffset: () => nextTelegramOffset,
|
|
865
1610
|
normalizeAddress: () => normalizeAddress,
|
|
@@ -884,6 +1629,7 @@ __export(index_exports, {
|
|
|
884
1629
|
redactSecret: () => redactSecret,
|
|
885
1630
|
redactSmsConfig: () => redactSmsConfig,
|
|
886
1631
|
redactTelegramConfig: () => redactTelegramConfig,
|
|
1632
|
+
renderSkillAsPrompt: () => renderSkillAsPrompt,
|
|
887
1633
|
requireBinary: () => requireBinary,
|
|
888
1634
|
requireWhisperModel: () => requireWhisperModel,
|
|
889
1635
|
resolveConfig: () => resolveConfig,
|
|
@@ -892,8 +1638,10 @@ __export(index_exports, {
|
|
|
892
1638
|
sanitizeEmail: () => sanitizeEmail,
|
|
893
1639
|
saveConfig: () => saveConfig,
|
|
894
1640
|
saveHostSession: () => saveHostSession,
|
|
1641
|
+
saveUserSkill: () => saveUserSkill,
|
|
895
1642
|
scanOutboundEmail: () => scanOutboundEmail,
|
|
896
1643
|
scoreEmail: () => scoreEmail,
|
|
1644
|
+
searchSkills: () => searchSkills,
|
|
897
1645
|
sendTelegramMessage: () => sendTelegramMessage,
|
|
898
1646
|
setOperatorEmail: () => setOperatorEmail,
|
|
899
1647
|
setTelegramWebhook: () => setTelegramWebhook,
|
|
@@ -906,10 +1654,12 @@ __export(index_exports, {
|
|
|
906
1654
|
threadIdFor: () => threadIdFor,
|
|
907
1655
|
tokenize: () => tokenize,
|
|
908
1656
|
tryJoin: () => tryJoin,
|
|
1657
|
+
userSkillsDir: () => userSkillsDir,
|
|
909
1658
|
validateApiUrl: () => validateApiUrl,
|
|
910
1659
|
validatePhoneMissionPolicy: () => validatePhoneMissionPolicy,
|
|
911
1660
|
validatePhoneMissionStart: () => validatePhoneMissionStart,
|
|
912
1661
|
validatePhoneTransportProfile: () => validatePhoneTransportProfile,
|
|
1662
|
+
validateSkill: () => validateSkill,
|
|
913
1663
|
validateTwilioSignature: () => validateTwilioSignature,
|
|
914
1664
|
webSearch: () => webSearch
|
|
915
1665
|
});
|
|
@@ -2016,14 +2766,14 @@ var StalwartAdmin = class {
|
|
|
2016
2766
|
if (!isValidDomain(domain)) {
|
|
2017
2767
|
throw new Error(`Invalid domain format: "${domain}"`);
|
|
2018
2768
|
}
|
|
2019
|
-
const { readFileSync:
|
|
2020
|
-
const { homedir:
|
|
2021
|
-
const { join:
|
|
2022
|
-
const configPath =
|
|
2769
|
+
const { readFileSync: readFileSync11, writeFileSync: writeFileSync12 } = await import("fs");
|
|
2770
|
+
const { homedir: homedir14 } = await import("os");
|
|
2771
|
+
const { join: join17 } = await import("path");
|
|
2772
|
+
const configPath = join17(homedir14(), ".agenticmail", "stalwart.toml");
|
|
2023
2773
|
try {
|
|
2024
|
-
let config =
|
|
2774
|
+
let config = readFileSync11(configPath, "utf-8");
|
|
2025
2775
|
config = config.replace(/^hostname\s*=\s*"[^"]*"/m, `hostname = "${escapeTomlString(domain)}"`);
|
|
2026
|
-
|
|
2776
|
+
writeFileSync12(configPath, config);
|
|
2027
2777
|
console.log(`[Stalwart] Updated hostname to "${domain}" in stalwart.toml`);
|
|
2028
2778
|
} catch (err) {
|
|
2029
2779
|
throw new Error(`Failed to set config server.hostname=${domain}`);
|
|
@@ -2032,15 +2782,15 @@ var StalwartAdmin = class {
|
|
|
2032
2782
|
// --- DKIM ---
|
|
2033
2783
|
/** Path to the host-side stalwart.toml (mounted read-only into container) */
|
|
2034
2784
|
get configPath() {
|
|
2035
|
-
const { homedir:
|
|
2036
|
-
const { join:
|
|
2037
|
-
return
|
|
2785
|
+
const { homedir: homedir14 } = require("os");
|
|
2786
|
+
const { join: join17 } = require("path");
|
|
2787
|
+
return join17(homedir14(), ".agenticmail", "stalwart.toml");
|
|
2038
2788
|
}
|
|
2039
2789
|
/** Path to host-side DKIM key directory */
|
|
2040
2790
|
get dkimDir() {
|
|
2041
|
-
const { homedir:
|
|
2042
|
-
const { join:
|
|
2043
|
-
return
|
|
2791
|
+
const { homedir: homedir14 } = require("os");
|
|
2792
|
+
const { join: join17 } = require("path");
|
|
2793
|
+
return join17(homedir14(), ".agenticmail");
|
|
2044
2794
|
}
|
|
2045
2795
|
/**
|
|
2046
2796
|
* Create/reuse a DKIM signing key for a domain.
|
|
@@ -2141,12 +2891,12 @@ var StalwartAdmin = class {
|
|
|
2141
2891
|
* This bypasses the need for a PTR record on the sending IP.
|
|
2142
2892
|
*/
|
|
2143
2893
|
async configureOutboundRelay(config) {
|
|
2144
|
-
const { readFileSync:
|
|
2145
|
-
const { homedir:
|
|
2146
|
-
const { join:
|
|
2894
|
+
const { readFileSync: readFileSync11, writeFileSync: writeFileSync12 } = await import("fs");
|
|
2895
|
+
const { homedir: homedir14 } = await import("os");
|
|
2896
|
+
const { join: join17 } = await import("path");
|
|
2147
2897
|
const routeName = config.routeName ?? "gmail";
|
|
2148
|
-
const tomlPath =
|
|
2149
|
-
let toml =
|
|
2898
|
+
const tomlPath = join17(homedir14(), ".agenticmail", "stalwart.toml");
|
|
2899
|
+
let toml = readFileSync11(tomlPath, "utf-8");
|
|
2150
2900
|
toml = toml.replace(/\n\[queue\.route\.gmail\][\s\S]*?(?=\n\[|$)/, "");
|
|
2151
2901
|
toml = toml.replace(/\n\[queue\.strategy\][\s\S]*?(?=\n\[|$)/, "");
|
|
2152
2902
|
const safeRouteName = routeName.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
@@ -2166,7 +2916,7 @@ auth.secret = "${escapeTomlString(config.password)}"
|
|
|
2166
2916
|
route = [ { if = "is_local_domain('', rcpt_domain)", then = "'local'" },
|
|
2167
2917
|
{ else = "'${safeRouteName}'" } ]
|
|
2168
2918
|
`;
|
|
2169
|
-
|
|
2919
|
+
writeFileSync12(tomlPath, toml, "utf-8");
|
|
2170
2920
|
await this.restartContainer();
|
|
2171
2921
|
}
|
|
2172
2922
|
};
|
|
@@ -8372,12 +9122,12 @@ var GatewayManager = class {
|
|
|
8372
9122
|
zone = await this.cfClient.createZone(domain);
|
|
8373
9123
|
}
|
|
8374
9124
|
const existingRecords = await this.cfClient.listDnsRecords(zone.id);
|
|
8375
|
-
const { homedir:
|
|
8376
|
-
const backupDir = (0, import_node_path4.join)(
|
|
9125
|
+
const { homedir: homedir14 } = await import("os");
|
|
9126
|
+
const backupDir = (0, import_node_path4.join)(homedir14(), ".agenticmail");
|
|
8377
9127
|
const backupPath = (0, import_node_path4.join)(backupDir, `dns-backup-${domain}-${Date.now()}.json`);
|
|
8378
|
-
const { writeFileSync:
|
|
8379
|
-
|
|
8380
|
-
|
|
9128
|
+
const { writeFileSync: writeFileSync12, mkdirSync: mkdirSync13 } = await import("fs");
|
|
9129
|
+
mkdirSync13(backupDir, { recursive: true });
|
|
9130
|
+
writeFileSync12(backupPath, JSON.stringify({
|
|
8381
9131
|
domain,
|
|
8382
9132
|
zoneId: zone.id,
|
|
8383
9133
|
backedUpAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -9661,12 +10411,46 @@ var SEARCH_EMAIL_TOOL = {
|
|
|
9661
10411
|
additionalProperties: false
|
|
9662
10412
|
}
|
|
9663
10413
|
};
|
|
10414
|
+
var SEARCH_SKILLS_TOOL = {
|
|
10415
|
+
type: "function",
|
|
10416
|
+
name: "search_skills",
|
|
10417
|
+
description: "Search your skill library for a playbook that fits the situation you just hit on this call (billing dispute, debt collector tactics, reservation deadlock, etc). Returns ranked summaries \u2014 pick the best match and pass its id to load_skill. Fast.",
|
|
10418
|
+
parameters: {
|
|
10419
|
+
type: "object",
|
|
10420
|
+
properties: {
|
|
10421
|
+
query: {
|
|
10422
|
+
type: "string",
|
|
10423
|
+
description: 'Plain-language description of the situation, e.g. "rep insists on a commitment date", "the restaurant is fully booked", "I need to dispute a recurring charge after cancellation".'
|
|
10424
|
+
}
|
|
10425
|
+
},
|
|
10426
|
+
required: ["query"],
|
|
10427
|
+
additionalProperties: false
|
|
10428
|
+
}
|
|
10429
|
+
};
|
|
10430
|
+
var LOAD_SKILL_TOOL = {
|
|
10431
|
+
type: "function",
|
|
10432
|
+
name: "load_skill",
|
|
10433
|
+
description: 'Load a skill playbook by id into your context for the rest of this call. The playbook (principles, scripted phrases, ordered tactics, hard boundaries, exit strategy) grounds your next turns. Always call search_skills first to find the right id. Before calling, say "hold on one moment" \u2014 loading is briefer than `ask_operator` but takes a beat.',
|
|
10434
|
+
parameters: {
|
|
10435
|
+
type: "object",
|
|
10436
|
+
properties: {
|
|
10437
|
+
id: {
|
|
10438
|
+
type: "string",
|
|
10439
|
+
description: 'Skill id (lowercase-hyphenated), e.g. "negotiate-bill-reduction". Get it from search_skills.'
|
|
10440
|
+
}
|
|
10441
|
+
},
|
|
10442
|
+
required: ["id"],
|
|
10443
|
+
additionalProperties: false
|
|
10444
|
+
}
|
|
10445
|
+
};
|
|
9664
10446
|
var REALTIME_TOOL_DEFINITIONS = {
|
|
9665
10447
|
ask_operator: ASK_OPERATOR_TOOL,
|
|
9666
10448
|
web_search: WEB_SEARCH_TOOL,
|
|
9667
10449
|
recall_memory: RECALL_MEMORY_TOOL,
|
|
9668
10450
|
get_datetime: GET_DATETIME_TOOL,
|
|
9669
|
-
search_email: SEARCH_EMAIL_TOOL
|
|
10451
|
+
search_email: SEARCH_EMAIL_TOOL,
|
|
10452
|
+
search_skills: SEARCH_SKILLS_TOOL,
|
|
10453
|
+
load_skill: LOAD_SKILL_TOOL
|
|
9670
10454
|
};
|
|
9671
10455
|
function buildRealtimeToolGuidance(tools) {
|
|
9672
10456
|
if (tools.length === 0) return "";
|
|
@@ -9685,6 +10469,16 @@ function buildRealtimeToolGuidance(tools) {
|
|
|
9685
10469
|
'The lookup tools (web_search, recall_memory, get_datetime, search_email) return in seconds \u2014 a brief "one moment" is plenty; no long hold is needed for these.'
|
|
9686
10470
|
);
|
|
9687
10471
|
}
|
|
10472
|
+
if (names.has("search_skills") && names.has("load_skill")) {
|
|
10473
|
+
lines.push(
|
|
10474
|
+
`Your SKILL LIBRARY contains playbooks for specific real-world phone situations \u2014 bill negotiation, debt-collector handling, restaurant booking, dispute filing, etc. Each playbook is a complete set of principles, scripted phrases, ordered tactics, boundaries, and exit strategy for that one situation. When you find yourself on the call without a clear next move \u2014 the rep brought up something you do not know how to handle, the conversation reached a stage that needs a specific tactic \u2014 load a skill instead of improvising:
|
|
10475
|
+
1. Tell the caller you need a moment: "Hold on one moment \u2014 let me check something."
|
|
10476
|
+
2. Call search_skills with a one-line description of the situation.
|
|
10477
|
+
3. Call load_skill with the id of the best match.
|
|
10478
|
+
4. Resume the call grounded in the playbook the load returned. Follow the playbook's tactic order, use its scripted phrases (paraphrased to match your voice), respect its hard boundaries, watch for its success / failure signals.
|
|
10479
|
+
A skill's rendered playbook is now part of your instructions for the rest of the call. You can load a second skill if a new situation comes up \u2014 but the model keeps a max of two loaded; a third load drops the oldest. Pick skills deliberately.`
|
|
10480
|
+
);
|
|
10481
|
+
}
|
|
9688
10482
|
return lines.join("\n");
|
|
9689
10483
|
}
|
|
9690
10484
|
function toolErrorText(err) {
|
|
@@ -9870,6 +10664,7 @@ var REALTIME_AUDIO_SAMPLE_RATE = 24e3;
|
|
|
9870
10664
|
var REALTIME_MAX_AUDIO_FRAME_BASE64 = 256 * 1024;
|
|
9871
10665
|
var MAX_PENDING_AUDIO_FRAMES = 200;
|
|
9872
10666
|
var REALTIME_TOOL_CALL_TIMEOUT_MS = 6 * 6e4;
|
|
10667
|
+
var MAX_LOADED_SKILLS = 2;
|
|
9873
10668
|
var MAX_IN_FLIGHT_TOOL_CALLS = 8;
|
|
9874
10669
|
var DEFAULT_PERSONA = "You are a helpful, professional voice assistant making a phone call on behalf of your operator. Speak naturally and concisely, the way a person would on a real call. Listen carefully, do not talk over the other party, and keep each turn short. Never invent facts; if you do not know something, say so. Do not reveal that you are an AI unless you are asked directly.";
|
|
9875
10670
|
function buildRealtimeInstructions(opts) {
|
|
@@ -9961,6 +10756,26 @@ var RealtimeVoiceBridge = class {
|
|
|
9961
10756
|
toolCallNames = /* @__PURE__ */ new Map();
|
|
9962
10757
|
/** `call_id`s whose tool call is currently executing. */
|
|
9963
10758
|
inFlightToolCalls = /* @__PURE__ */ new Set();
|
|
10759
|
+
/**
|
|
10760
|
+
* Mid-call skills loaded into the session so far, FIFO. Earliest at
|
|
10761
|
+
* index 0; cap at {@link MAX_LOADED_SKILLS}. When a (cap+1)th skill
|
|
10762
|
+
* is loaded the oldest one drops out — the model can't usefully
|
|
10763
|
+
* hold five playbooks in working memory at once, so we keep the
|
|
10764
|
+
* working set narrow on purpose.
|
|
10765
|
+
*/
|
|
10766
|
+
loadedSkills = [];
|
|
10767
|
+
/**
|
|
10768
|
+
* The original `instructions` string from the session.update sent at
|
|
10769
|
+
* open. We keep a private copy because every mid-call skill load
|
|
10770
|
+
* issues a fresh `session.update` whose `instructions` is built as:
|
|
10771
|
+
*
|
|
10772
|
+
* baseInstructions + "\n\n" + renderedSkill1 + "\n\n" + renderedSkill2 …
|
|
10773
|
+
*
|
|
10774
|
+
* Without this snapshot, successive loads would compound — the second
|
|
10775
|
+
* load would see "base + skill1" as the base and append skill2 to
|
|
10776
|
+
* THAT, eventually drifting unboundedly.
|
|
10777
|
+
*/
|
|
10778
|
+
baseInstructions = "";
|
|
9964
10779
|
constructor(opts) {
|
|
9965
10780
|
const carrier = opts.carrier ?? opts.elks;
|
|
9966
10781
|
if (!carrier) {
|
|
@@ -9997,6 +10812,10 @@ var RealtimeVoiceBridge = class {
|
|
|
9997
10812
|
handleOpenAIOpen() {
|
|
9998
10813
|
if (this.ended || this.openaiReady) return;
|
|
9999
10814
|
this.openaiReady = true;
|
|
10815
|
+
const sess = this.sessionConfig?.session;
|
|
10816
|
+
if (sess && typeof sess.instructions === "string") {
|
|
10817
|
+
this.baseInstructions = sess.instructions;
|
|
10818
|
+
}
|
|
10000
10819
|
this.safeSend(this.openai, this.sessionConfig);
|
|
10001
10820
|
this.safeSend(this.openai, { type: "response.create" });
|
|
10002
10821
|
for (const audio of this.pendingAudio.splice(0)) {
|
|
@@ -10007,6 +10826,74 @@ var RealtimeVoiceBridge = class {
|
|
|
10007
10826
|
handleOpenAIClose() {
|
|
10008
10827
|
this.end("openai-closed");
|
|
10009
10828
|
}
|
|
10829
|
+
/**
|
|
10830
|
+
* Load a skill playbook into the live OpenAI Realtime session for
|
|
10831
|
+
* the rest of the call.
|
|
10832
|
+
*
|
|
10833
|
+
* Mechanics:
|
|
10834
|
+
* 1. Resolve the skill JSON via the skills registry (file on disk).
|
|
10835
|
+
* 2. Append the rendered skill text to the agent's working
|
|
10836
|
+
* instructions and re-send a `session.update` carrying ONLY
|
|
10837
|
+
* the new `instructions` field. The OpenAI Realtime API
|
|
10838
|
+
* supports partial session.update — we don't have to re-send
|
|
10839
|
+
* audio config, tools, voice, etc.
|
|
10840
|
+
* 3. Track which skills are loaded so we (a) FIFO-evict the
|
|
10841
|
+
* oldest when the cap is hit and (b) include every still-
|
|
10842
|
+
* loaded skill in the next composed instructions.
|
|
10843
|
+
* 4. Emit a transcript marker so the mission record shows the
|
|
10844
|
+
* adaptation ("[skill loaded: Negotiate a Bill Reduction
|
|
10845
|
+
* v1.0.0]"). Useful for post-call review and for the build
|
|
10846
|
+
* farm's telemetry on which skills actually got reached for.
|
|
10847
|
+
*
|
|
10848
|
+
* Returns an object the {@link load_skill} tool handler can serialise
|
|
10849
|
+
* back to the model: `ok: true` plus the skill name + version on
|
|
10850
|
+
* success, `ok: false` plus a short reason on failure (unknown id,
|
|
10851
|
+
* call ended, registry I/O error). Never throws — a buggy registry
|
|
10852
|
+
* or a missing file must not crash the bridge mid-call.
|
|
10853
|
+
*
|
|
10854
|
+
* Phase 2 of the skill library (`docs/skill-library-plan.md`).
|
|
10855
|
+
*/
|
|
10856
|
+
async loadSkillIntoSession(skillId) {
|
|
10857
|
+
if (this.ended) return { ok: false, message: "Call has already ended; cannot load a skill now." };
|
|
10858
|
+
if (!this.openaiReady) return { ok: false, message: "Session is not ready yet; try again in a moment." };
|
|
10859
|
+
if (this.loadedSkills.some((s) => s.id === skillId)) {
|
|
10860
|
+
const existing = this.loadedSkills.find((s) => s.id === skillId);
|
|
10861
|
+
return { ok: true, message: `Skill "${skillId}" is already loaded.`, name: skillId, version: existing.version };
|
|
10862
|
+
}
|
|
10863
|
+
let loadSkill2;
|
|
10864
|
+
let renderSkillAsPrompt2;
|
|
10865
|
+
try {
|
|
10866
|
+
({ loadSkill: loadSkill2, renderSkillAsPrompt: renderSkillAsPrompt2 } = await Promise.resolve().then(() => (init_skills(), skills_exports)));
|
|
10867
|
+
} catch (err) {
|
|
10868
|
+
return { ok: false, message: `Skill registry unavailable: ${errorText(err)}` };
|
|
10869
|
+
}
|
|
10870
|
+
const skill = loadSkill2(skillId);
|
|
10871
|
+
if (!skill) {
|
|
10872
|
+
return { ok: false, message: `No skill found with id "${skillId}". Call search_skills first to find the right id.` };
|
|
10873
|
+
}
|
|
10874
|
+
const rendered = renderSkillAsPrompt2(skill);
|
|
10875
|
+
while (this.loadedSkills.length >= MAX_LOADED_SKILLS) {
|
|
10876
|
+
const dropped = this.loadedSkills.shift();
|
|
10877
|
+
if (dropped) {
|
|
10878
|
+
this.emitTranscript("system", `[skill unloaded for working-memory limit: ${dropped.id} v${dropped.version}]`);
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10881
|
+
this.loadedSkills.push({ id: skill.id, version: skill.version, renderedPrompt: rendered });
|
|
10882
|
+
const composed = [
|
|
10883
|
+
this.baseInstructions,
|
|
10884
|
+
...this.loadedSkills.map((s) => s.renderedPrompt)
|
|
10885
|
+
].filter((s) => s && s.length > 0).join("\n\n");
|
|
10886
|
+
this.safeSend(this.openai, {
|
|
10887
|
+
type: "session.update",
|
|
10888
|
+
session: { instructions: composed }
|
|
10889
|
+
});
|
|
10890
|
+
this.emitTranscript("system", `[skill loaded: ${skill.name} v${skill.version}]`);
|
|
10891
|
+
return { ok: true, message: `Loaded skill: ${skill.name} (v${skill.version})`, name: skill.name, version: skill.version };
|
|
10892
|
+
}
|
|
10893
|
+
/** The list of skills currently loaded into the session (FIFO-ordered). */
|
|
10894
|
+
get loadedSkillIds() {
|
|
10895
|
+
return this.loadedSkills.map((s) => s.id);
|
|
10896
|
+
}
|
|
10010
10897
|
/** Call when the OpenAI socket errors. */
|
|
10011
10898
|
handleOpenAIError(err) {
|
|
10012
10899
|
this.emitTranscript("system", `OpenAI Realtime error: ${errorText(err)}`);
|
|
@@ -10467,7 +11354,7 @@ try {
|
|
|
10467
11354
|
}
|
|
10468
11355
|
|
|
10469
11356
|
// src/util/safe-path.ts
|
|
10470
|
-
var
|
|
11357
|
+
var import_node_path6 = require("path");
|
|
10471
11358
|
var PathTraversalError = class extends Error {
|
|
10472
11359
|
constructor(baseDir, parts) {
|
|
10473
11360
|
super(
|
|
@@ -10490,14 +11377,14 @@ function safeJoin(baseDir, ...partsAndOpts) {
|
|
|
10490
11377
|
}
|
|
10491
11378
|
if (!opts.allowAbsolute) {
|
|
10492
11379
|
for (const part of parts) {
|
|
10493
|
-
if ((0,
|
|
11380
|
+
if ((0, import_node_path6.isAbsolute)(part)) {
|
|
10494
11381
|
throw new PathTraversalError(baseDir, parts);
|
|
10495
11382
|
}
|
|
10496
11383
|
}
|
|
10497
11384
|
}
|
|
10498
|
-
const resolvedBase = (0,
|
|
10499
|
-
const resolved = (0,
|
|
10500
|
-
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase +
|
|
11385
|
+
const resolvedBase = (0, import_node_path6.resolve)(baseDir);
|
|
11386
|
+
const resolved = (0, import_node_path6.resolve)(resolvedBase, ...parts);
|
|
11387
|
+
if (resolved !== resolvedBase && !resolved.startsWith(resolvedBase + import_node_path6.sep)) {
|
|
10501
11388
|
throw new PathTraversalError(baseDir, parts);
|
|
10502
11389
|
}
|
|
10503
11390
|
return resolved;
|
|
@@ -10511,9 +11398,9 @@ function tryJoin(baseDir, ...parts) {
|
|
|
10511
11398
|
}
|
|
10512
11399
|
}
|
|
10513
11400
|
function assertWithinBase(baseDir, candidate) {
|
|
10514
|
-
const resolvedBase = (0,
|
|
10515
|
-
const resolvedCandidate = (0,
|
|
10516
|
-
if (resolvedCandidate !== resolvedBase && !resolvedCandidate.startsWith(resolvedBase +
|
|
11401
|
+
const resolvedBase = (0, import_node_path6.resolve)(baseDir);
|
|
11402
|
+
const resolvedCandidate = (0, import_node_path6.resolve)(candidate);
|
|
11403
|
+
if (resolvedCandidate !== resolvedBase && !resolvedCandidate.startsWith(resolvedBase + import_node_path6.sep)) {
|
|
10517
11404
|
throw new PathTraversalError(baseDir, [candidate]);
|
|
10518
11405
|
}
|
|
10519
11406
|
return resolvedCandidate;
|
|
@@ -10566,19 +11453,19 @@ function redactObject(input, _depth = 0) {
|
|
|
10566
11453
|
}
|
|
10567
11454
|
|
|
10568
11455
|
// src/operator-prefs.ts
|
|
10569
|
-
var
|
|
10570
|
-
var
|
|
10571
|
-
var
|
|
11456
|
+
var import_node_fs5 = require("fs");
|
|
11457
|
+
var import_node_os5 = require("os");
|
|
11458
|
+
var import_node_path7 = require("path");
|
|
10572
11459
|
function dir() {
|
|
10573
|
-
return (0,
|
|
11460
|
+
return (0, import_node_path7.join)((0, import_node_os5.homedir)(), ".agenticmail");
|
|
10574
11461
|
}
|
|
10575
11462
|
function path() {
|
|
10576
|
-
return (0,
|
|
11463
|
+
return (0, import_node_path7.join)(dir(), "operator-prefs.json");
|
|
10577
11464
|
}
|
|
10578
11465
|
function readFile() {
|
|
10579
|
-
if (!(0,
|
|
11466
|
+
if (!(0, import_node_fs5.existsSync)(path())) return { version: 1 };
|
|
10580
11467
|
try {
|
|
10581
|
-
const raw = (0,
|
|
11468
|
+
const raw = (0, import_node_fs5.readFileSync)(path(), "utf-8");
|
|
10582
11469
|
if (!raw.trim()) return { version: 1 };
|
|
10583
11470
|
const parsed = JSON.parse(raw);
|
|
10584
11471
|
return { version: 1, operatorEmail: typeof parsed.operatorEmail === "string" ? parsed.operatorEmail : void 0 };
|
|
@@ -10589,10 +11476,10 @@ function readFile() {
|
|
|
10589
11476
|
function writeFile(shape) {
|
|
10590
11477
|
const d = dir();
|
|
10591
11478
|
const p = path();
|
|
10592
|
-
if (!(0,
|
|
11479
|
+
if (!(0, import_node_fs5.existsSync)(d)) (0, import_node_fs5.mkdirSync)(d, { recursive: true });
|
|
10593
11480
|
const tmp = `${p}.agenticmail-tmp-${process.pid}`;
|
|
10594
|
-
(0,
|
|
10595
|
-
(0,
|
|
11481
|
+
(0, import_node_fs5.writeFileSync)(tmp, JSON.stringify(shape, null, 2), "utf-8");
|
|
11482
|
+
(0, import_node_fs5.renameSync)(tmp, p);
|
|
10596
11483
|
}
|
|
10597
11484
|
function getOperatorEmail() {
|
|
10598
11485
|
const shape = readFile();
|
|
@@ -10621,21 +11508,21 @@ function operatorPrefsStoragePath() {
|
|
|
10621
11508
|
}
|
|
10622
11509
|
|
|
10623
11510
|
// src/host-sessions.ts
|
|
10624
|
-
var
|
|
10625
|
-
var
|
|
10626
|
-
var
|
|
11511
|
+
var import_node_fs6 = require("fs");
|
|
11512
|
+
var import_node_path8 = require("path");
|
|
11513
|
+
var import_node_os6 = require("os");
|
|
10627
11514
|
function storageDir() {
|
|
10628
|
-
return (0,
|
|
11515
|
+
return (0, import_node_path8.join)((0, import_node_os6.homedir)(), ".agenticmail");
|
|
10629
11516
|
}
|
|
10630
11517
|
function storagePath() {
|
|
10631
|
-
return (0,
|
|
11518
|
+
return (0, import_node_path8.join)(storageDir(), "host-sessions.json");
|
|
10632
11519
|
}
|
|
10633
11520
|
var DEFAULT_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
10634
11521
|
function readFile2() {
|
|
10635
11522
|
const p = storagePath();
|
|
10636
|
-
if (!(0,
|
|
11523
|
+
if (!(0, import_node_fs6.existsSync)(p)) return { version: 1, sessions: {} };
|
|
10637
11524
|
try {
|
|
10638
|
-
const raw = (0,
|
|
11525
|
+
const raw = (0, import_node_fs6.readFileSync)(p, "utf-8");
|
|
10639
11526
|
if (!raw.trim()) return { version: 1, sessions: {} };
|
|
10640
11527
|
const parsed = JSON.parse(raw);
|
|
10641
11528
|
return {
|
|
@@ -10649,10 +11536,10 @@ function readFile2() {
|
|
|
10649
11536
|
function writeFile2(shape) {
|
|
10650
11537
|
const dir2 = storageDir();
|
|
10651
11538
|
const p = storagePath();
|
|
10652
|
-
if (!(0,
|
|
11539
|
+
if (!(0, import_node_fs6.existsSync)(dir2)) (0, import_node_fs6.mkdirSync)(dir2, { recursive: true });
|
|
10653
11540
|
const tmp = `${p}.agenticmail-tmp-${process.pid}`;
|
|
10654
|
-
(0,
|
|
10655
|
-
(0,
|
|
11541
|
+
(0, import_node_fs6.writeFileSync)(tmp, JSON.stringify(shape, null, 2), "utf-8");
|
|
11542
|
+
(0, import_node_fs6.renameSync)(tmp, p);
|
|
10656
11543
|
}
|
|
10657
11544
|
function saveHostSession(host, session) {
|
|
10658
11545
|
if (!session.sessionId) return;
|
|
@@ -10827,15 +11714,15 @@ function buildApiUrl(baseOrigin, pathAndQuery) {
|
|
|
10827
11714
|
|
|
10828
11715
|
// src/setup/index.ts
|
|
10829
11716
|
var import_node_crypto6 = require("crypto");
|
|
10830
|
-
var
|
|
10831
|
-
var
|
|
10832
|
-
var
|
|
11717
|
+
var import_node_fs10 = require("fs");
|
|
11718
|
+
var import_node_path12 = require("path");
|
|
11719
|
+
var import_node_os10 = require("os");
|
|
10833
11720
|
|
|
10834
11721
|
// src/setup/deps.ts
|
|
10835
11722
|
var import_node_child_process2 = require("child_process");
|
|
10836
|
-
var
|
|
10837
|
-
var
|
|
10838
|
-
var
|
|
11723
|
+
var import_node_fs7 = require("fs");
|
|
11724
|
+
var import_node_path9 = require("path");
|
|
11725
|
+
var import_node_os7 = require("os");
|
|
10839
11726
|
var DependencyChecker = class {
|
|
10840
11727
|
async checkAll() {
|
|
10841
11728
|
return Promise.all([
|
|
@@ -10885,8 +11772,8 @@ var DependencyChecker = class {
|
|
|
10885
11772
|
}
|
|
10886
11773
|
}
|
|
10887
11774
|
async checkCloudflared() {
|
|
10888
|
-
const binPath = (0,
|
|
10889
|
-
if ((0,
|
|
11775
|
+
const binPath = (0, import_node_path9.join)((0, import_node_os7.homedir)(), ".agenticmail", "bin", "cloudflared");
|
|
11776
|
+
if ((0, import_node_fs7.existsSync)(binPath)) {
|
|
10890
11777
|
let version;
|
|
10891
11778
|
try {
|
|
10892
11779
|
const output = (0, import_node_child_process2.execFileSync)(binPath, ["--version"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
@@ -10908,10 +11795,10 @@ var DependencyChecker = class {
|
|
|
10908
11795
|
|
|
10909
11796
|
// src/setup/installer.ts
|
|
10910
11797
|
var import_node_child_process3 = require("child_process");
|
|
10911
|
-
var
|
|
11798
|
+
var import_node_fs8 = require("fs");
|
|
10912
11799
|
var import_promises2 = require("fs/promises");
|
|
10913
|
-
var
|
|
10914
|
-
var
|
|
11800
|
+
var import_node_path10 = require("path");
|
|
11801
|
+
var import_node_os8 = require("os");
|
|
10915
11802
|
function runShellWithRollingOutput(cmd, opts = {}) {
|
|
10916
11803
|
const maxLines = opts.maxLines ?? 20;
|
|
10917
11804
|
const timeout = opts.timeout ?? 3e5;
|
|
@@ -11021,7 +11908,7 @@ var DependencyInstaller = class {
|
|
|
11021
11908
|
*/
|
|
11022
11909
|
async installDocker() {
|
|
11023
11910
|
if (this.isDockerReady()) return;
|
|
11024
|
-
const os = (0,
|
|
11911
|
+
const os = (0, import_node_os8.platform)();
|
|
11025
11912
|
if (os === "darwin") {
|
|
11026
11913
|
await this.installDockerMac();
|
|
11027
11914
|
} else if (os === "linux") {
|
|
@@ -11087,15 +11974,15 @@ var DependencyInstaller = class {
|
|
|
11087
11974
|
try {
|
|
11088
11975
|
const composeBin = (0, import_node_child_process3.execFileSync)("which", ["docker-compose"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
11089
11976
|
if (!composeBin) return;
|
|
11090
|
-
const pluginDir = (0,
|
|
11091
|
-
const pluginPath = (0,
|
|
11092
|
-
if ((0,
|
|
11977
|
+
const pluginDir = (0, import_node_path10.join)((0, import_node_os8.homedir)(), ".docker", "cli-plugins");
|
|
11978
|
+
const pluginPath = (0, import_node_path10.join)(pluginDir, "docker-compose");
|
|
11979
|
+
if ((0, import_node_fs8.existsSync)(pluginPath)) return;
|
|
11093
11980
|
try {
|
|
11094
|
-
(0,
|
|
11981
|
+
(0, import_node_fs8.mkdirSync)(pluginDir, { recursive: true });
|
|
11095
11982
|
} catch {
|
|
11096
11983
|
}
|
|
11097
11984
|
try {
|
|
11098
|
-
(0,
|
|
11985
|
+
(0, import_node_fs8.symlinkSync)(composeBin, pluginPath);
|
|
11099
11986
|
} catch {
|
|
11100
11987
|
}
|
|
11101
11988
|
} catch {
|
|
@@ -11154,9 +12041,9 @@ var DependencyInstaller = class {
|
|
|
11154
12041
|
return;
|
|
11155
12042
|
}
|
|
11156
12043
|
this.onProgress("__progress__:5:Installing Docker Engine...");
|
|
11157
|
-
const tmpDir = (0,
|
|
12044
|
+
const tmpDir = (0, import_node_path10.join)((0, import_node_os8.homedir)(), ".agenticmail", "tmp");
|
|
11158
12045
|
await (0, import_promises2.mkdir)(tmpDir, { recursive: true });
|
|
11159
|
-
const scriptPath = (0,
|
|
12046
|
+
const scriptPath = (0, import_node_path10.join)(tmpDir, "install-docker.sh");
|
|
11160
12047
|
const dlResult = await runShellWithRollingOutput(
|
|
11161
12048
|
`curl -fsSL https://get.docker.com -o "${scriptPath}" && sudo sh "${scriptPath}"`,
|
|
11162
12049
|
{ timeout: 3e5 }
|
|
@@ -11224,8 +12111,8 @@ var DependencyInstaller = class {
|
|
|
11224
12111
|
}
|
|
11225
12112
|
try {
|
|
11226
12113
|
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
11227
|
-
const dockerExe = (0,
|
|
11228
|
-
if ((0,
|
|
12114
|
+
const dockerExe = (0, import_node_path10.join)(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
12115
|
+
if ((0, import_node_fs8.existsSync)(dockerExe)) {
|
|
11229
12116
|
(0, import_node_child_process3.execSync)(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
11230
12117
|
}
|
|
11231
12118
|
} catch {
|
|
@@ -11275,8 +12162,8 @@ var DependencyInstaller = class {
|
|
|
11275
12162
|
this.onProgress("__progress__:70:Docker Desktop installed. Starting...");
|
|
11276
12163
|
try {
|
|
11277
12164
|
const programFiles = process.env["ProgramFiles"] || "C:\\Program Files";
|
|
11278
|
-
const dockerExe = (0,
|
|
11279
|
-
if ((0,
|
|
12165
|
+
const dockerExe = (0, import_node_path10.join)(programFiles, "Docker", "Docker", "Docker Desktop.exe");
|
|
12166
|
+
if ((0, import_node_fs8.existsSync)(dockerExe)) {
|
|
11280
12167
|
(0, import_node_child_process3.execSync)(`start "" "${dockerExe}"`, { timeout: 1e4, stdio: "ignore", shell: "cmd.exe" });
|
|
11281
12168
|
}
|
|
11282
12169
|
} catch {
|
|
@@ -11313,12 +12200,12 @@ var DependencyInstaller = class {
|
|
|
11313
12200
|
* Start the Stalwart mail server Docker container.
|
|
11314
12201
|
*/
|
|
11315
12202
|
async startStalwart(composePath) {
|
|
11316
|
-
if (!(0,
|
|
12203
|
+
if (!(0, import_node_fs8.existsSync)(composePath)) {
|
|
11317
12204
|
throw new Error(`docker-compose.yml not found at: ${composePath}`);
|
|
11318
12205
|
}
|
|
11319
12206
|
if (!this.isDockerReady()) {
|
|
11320
12207
|
this.onProgress("Starting Docker...");
|
|
11321
|
-
const os = (0,
|
|
12208
|
+
const os = (0, import_node_os8.platform)();
|
|
11322
12209
|
if (os === "darwin") {
|
|
11323
12210
|
await this.startColima();
|
|
11324
12211
|
} else if (os === "win32") {
|
|
@@ -11336,7 +12223,7 @@ var DependencyInstaller = class {
|
|
|
11336
12223
|
}
|
|
11337
12224
|
}
|
|
11338
12225
|
this.onProgress("__progress__:10:Pulling mail server image...");
|
|
11339
|
-
if ((0,
|
|
12226
|
+
if ((0, import_node_os8.platform)() === "darwin") this.linkComposePlugin();
|
|
11340
12227
|
let composeResult = await runSilent("docker", ["compose", "-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
11341
12228
|
if (composeResult.exitCode !== 0 && hasCommand("docker-compose")) {
|
|
11342
12229
|
composeResult = await runSilent("docker-compose", ["-f", composePath, "up", "-d"], { timeout: 12e4 });
|
|
@@ -11371,22 +12258,22 @@ var DependencyInstaller = class {
|
|
|
11371
12258
|
* Returns the path to the installed binary.
|
|
11372
12259
|
*/
|
|
11373
12260
|
async installCloudflared() {
|
|
11374
|
-
const os = (0,
|
|
11375
|
-
const binDir = (0,
|
|
12261
|
+
const os = (0, import_node_os8.platform)();
|
|
12262
|
+
const binDir = (0, import_node_path10.join)((0, import_node_os8.homedir)(), ".agenticmail", "bin");
|
|
11376
12263
|
const binName = os === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
11377
|
-
const binPath = (0,
|
|
11378
|
-
if ((0,
|
|
12264
|
+
const binPath = (0, import_node_path10.join)(binDir, binName);
|
|
12265
|
+
if ((0, import_node_fs8.existsSync)(binPath)) {
|
|
11379
12266
|
return binPath;
|
|
11380
12267
|
}
|
|
11381
12268
|
try {
|
|
11382
12269
|
const whichCmd = os === "win32" ? "where" : "which";
|
|
11383
12270
|
const sysPath = (0, import_node_child_process3.execFileSync)(whichCmd, ["cloudflared"], { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split("\n")[0];
|
|
11384
|
-
if (sysPath && (0,
|
|
12271
|
+
if (sysPath && (0, import_node_fs8.existsSync)(sysPath)) return sysPath;
|
|
11385
12272
|
} catch {
|
|
11386
12273
|
}
|
|
11387
12274
|
this.onProgress("Downloading cloudflared...");
|
|
11388
12275
|
await (0, import_promises2.mkdir)(binDir, { recursive: true });
|
|
11389
|
-
const cpu = (0,
|
|
12276
|
+
const cpu = (0, import_node_os8.arch)();
|
|
11390
12277
|
const archName = cpu === "arm64" ? "arm64" : "amd64";
|
|
11391
12278
|
let downloadUrl;
|
|
11392
12279
|
if (os === "darwin") {
|
|
@@ -11404,7 +12291,7 @@ var DependencyInstaller = class {
|
|
|
11404
12291
|
}
|
|
11405
12292
|
const buffer = Buffer.from(await response.arrayBuffer());
|
|
11406
12293
|
if (os === "darwin") {
|
|
11407
|
-
const tgzPath = (0,
|
|
12294
|
+
const tgzPath = (0, import_node_path10.join)(binDir, "cloudflared.tgz");
|
|
11408
12295
|
await (0, import_promises2.writeFile)(tgzPath, buffer);
|
|
11409
12296
|
try {
|
|
11410
12297
|
(0, import_node_child_process3.execFileSync)("tar", ["-xzf", tgzPath, "-C", binDir, "cloudflared"], { timeout: 15e3, stdio: "ignore" });
|
|
@@ -11421,7 +12308,7 @@ var DependencyInstaller = class {
|
|
|
11421
12308
|
if (os !== "win32") await (0, import_promises2.chmod)(tmpPath, 493);
|
|
11422
12309
|
await (0, import_promises2.rename)(tmpPath, binPath);
|
|
11423
12310
|
}
|
|
11424
|
-
if (!(0,
|
|
12311
|
+
if (!(0, import_node_fs8.existsSync)(binPath)) {
|
|
11425
12312
|
throw new Error("cloudflared download succeeded but binary not found after extraction");
|
|
11426
12313
|
}
|
|
11427
12314
|
this.onProgress("cloudflared installed");
|
|
@@ -11439,23 +12326,23 @@ var DependencyInstaller = class {
|
|
|
11439
12326
|
|
|
11440
12327
|
// src/setup/service.ts
|
|
11441
12328
|
var import_node_child_process4 = require("child_process");
|
|
11442
|
-
var
|
|
11443
|
-
var
|
|
11444
|
-
var
|
|
12329
|
+
var import_node_fs9 = require("fs");
|
|
12330
|
+
var import_node_path11 = require("path");
|
|
12331
|
+
var import_node_os9 = require("os");
|
|
11445
12332
|
var import_node_module2 = require("module");
|
|
11446
|
-
var
|
|
12333
|
+
var import_meta3 = {};
|
|
11447
12334
|
var PLIST_LABEL = "com.agenticmail.server";
|
|
11448
12335
|
var SYSTEMD_UNIT = "agenticmail.service";
|
|
11449
12336
|
var ServiceManager = class {
|
|
11450
|
-
os = (0,
|
|
12337
|
+
os = (0, import_node_os9.platform)();
|
|
11451
12338
|
/**
|
|
11452
12339
|
* Get the path to the service file.
|
|
11453
12340
|
*/
|
|
11454
12341
|
getServicePath() {
|
|
11455
12342
|
if (this.os === "darwin") {
|
|
11456
|
-
return (0,
|
|
12343
|
+
return (0, import_node_path11.join)((0, import_node_os9.homedir)(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
|
|
11457
12344
|
} else {
|
|
11458
|
-
return (0,
|
|
12345
|
+
return (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
11459
12346
|
}
|
|
11460
12347
|
}
|
|
11461
12348
|
/**
|
|
@@ -11487,42 +12374,42 @@ var ServiceManager = class {
|
|
|
11487
12374
|
*/
|
|
11488
12375
|
getApiEntryPath() {
|
|
11489
12376
|
try {
|
|
11490
|
-
const req = (0, import_node_module2.createRequire)(
|
|
12377
|
+
const req = (0, import_node_module2.createRequire)(import_meta3.url);
|
|
11491
12378
|
const resolved = req.resolve("@agenticmail/api");
|
|
11492
|
-
if ((0,
|
|
12379
|
+
if ((0, import_node_fs9.existsSync)(resolved)) return resolved;
|
|
11493
12380
|
} catch {
|
|
11494
12381
|
}
|
|
11495
12382
|
const parentPackages = [
|
|
11496
|
-
(0,
|
|
12383
|
+
(0, import_node_path11.join)("@agenticmail", "cli"),
|
|
11497
12384
|
// current scoped package
|
|
11498
12385
|
"agenticmail"
|
|
11499
12386
|
// legacy unscoped package
|
|
11500
12387
|
];
|
|
11501
12388
|
const baseDirs = [
|
|
11502
12389
|
// user-local install
|
|
11503
|
-
(0,
|
|
12390
|
+
(0, import_node_path11.join)((0, import_node_os9.homedir)(), "node_modules")
|
|
11504
12391
|
];
|
|
11505
12392
|
try {
|
|
11506
12393
|
const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
11507
|
-
baseDirs.push((0,
|
|
11508
|
-
baseDirs.push((0,
|
|
12394
|
+
baseDirs.push((0, import_node_path11.join)(prefix, "lib", "node_modules"));
|
|
12395
|
+
baseDirs.push((0, import_node_path11.join)(prefix, "node_modules"));
|
|
11509
12396
|
} catch {
|
|
11510
12397
|
}
|
|
11511
12398
|
baseDirs.push("/opt/homebrew/lib/node_modules");
|
|
11512
12399
|
baseDirs.push("/usr/local/lib/node_modules");
|
|
11513
12400
|
for (const base of baseDirs) {
|
|
11514
|
-
const sibling = (0,
|
|
11515
|
-
if ((0,
|
|
12401
|
+
const sibling = (0, import_node_path11.join)(base, "@agenticmail", "api", "dist", "index.js");
|
|
12402
|
+
if ((0, import_node_fs9.existsSync)(sibling)) return sibling;
|
|
11516
12403
|
for (const parent of parentPackages) {
|
|
11517
|
-
const nested = (0,
|
|
11518
|
-
if ((0,
|
|
12404
|
+
const nested = (0, import_node_path11.join)(base, parent, "node_modules", "@agenticmail", "api", "dist", "index.js");
|
|
12405
|
+
if ((0, import_node_fs9.existsSync)(nested)) return nested;
|
|
11519
12406
|
}
|
|
11520
12407
|
}
|
|
11521
|
-
const dataDir = (0,
|
|
11522
|
-
const entryCache = (0,
|
|
11523
|
-
if ((0,
|
|
11524
|
-
const cached = (0,
|
|
11525
|
-
if (cached && (0,
|
|
12408
|
+
const dataDir = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail");
|
|
12409
|
+
const entryCache = (0, import_node_path11.join)(dataDir, "api-entry.path");
|
|
12410
|
+
if ((0, import_node_fs9.existsSync)(entryCache)) {
|
|
12411
|
+
const cached = (0, import_node_fs9.readFileSync)(entryCache, "utf-8").trim();
|
|
12412
|
+
if (cached && (0, import_node_fs9.existsSync)(cached)) return cached;
|
|
11526
12413
|
}
|
|
11527
12414
|
throw new Error("Could not find @agenticmail/api entry point. Run `agenticmail start` first to populate the cache.");
|
|
11528
12415
|
}
|
|
@@ -11530,9 +12417,9 @@ var ServiceManager = class {
|
|
|
11530
12417
|
* Cache the API entry path so the service can find it later.
|
|
11531
12418
|
*/
|
|
11532
12419
|
cacheApiEntryPath(entryPath) {
|
|
11533
|
-
const dataDir = (0,
|
|
11534
|
-
if (!(0,
|
|
11535
|
-
(0,
|
|
12420
|
+
const dataDir = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail");
|
|
12421
|
+
if (!(0, import_node_fs9.existsSync)(dataDir)) (0, import_node_fs9.mkdirSync)(dataDir, { recursive: true });
|
|
12422
|
+
(0, import_node_fs9.writeFileSync)((0, import_node_path11.join)(dataDir, "api-entry.path"), entryPath);
|
|
11536
12423
|
}
|
|
11537
12424
|
/**
|
|
11538
12425
|
* Get the current package version.
|
|
@@ -11543,38 +12430,38 @@ var ServiceManager = class {
|
|
|
11543
12430
|
*/
|
|
11544
12431
|
getVersion() {
|
|
11545
12432
|
try {
|
|
11546
|
-
const req = (0, import_node_module2.createRequire)(
|
|
12433
|
+
const req = (0, import_node_module2.createRequire)(import_meta3.url);
|
|
11547
12434
|
const pkgJson = req.resolve("@agenticmail/cli/package.json");
|
|
11548
|
-
if ((0,
|
|
11549
|
-
const pkg = JSON.parse((0,
|
|
12435
|
+
if ((0, import_node_fs9.existsSync)(pkgJson)) {
|
|
12436
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(pkgJson, "utf-8"));
|
|
11550
12437
|
if (pkg.version) return pkg.version;
|
|
11551
12438
|
}
|
|
11552
12439
|
} catch {
|
|
11553
12440
|
}
|
|
11554
12441
|
try {
|
|
11555
12442
|
const apiEntry = this.getApiEntryPath();
|
|
11556
|
-
const apiPkg = (0,
|
|
11557
|
-
if ((0,
|
|
11558
|
-
const pkg = JSON.parse((0,
|
|
12443
|
+
const apiPkg = (0, import_node_path11.join)(apiEntry, "..", "..", "package.json");
|
|
12444
|
+
if ((0, import_node_fs9.existsSync)(apiPkg)) {
|
|
12445
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(apiPkg, "utf-8"));
|
|
11559
12446
|
if (pkg.version) return pkg.version;
|
|
11560
12447
|
}
|
|
11561
12448
|
} catch {
|
|
11562
12449
|
}
|
|
11563
12450
|
const candidates = [
|
|
11564
|
-
(0,
|
|
11565
|
-
(0,
|
|
11566
|
-
(0,
|
|
12451
|
+
(0, import_node_path11.join)((0, import_node_os9.homedir)(), "node_modules", "@agenticmail", "cli", "package.json"),
|
|
12452
|
+
(0, import_node_path11.join)((0, import_node_os9.homedir)(), "node_modules", "agenticmail", "package.json"),
|
|
12453
|
+
(0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "package-version.json")
|
|
11567
12454
|
];
|
|
11568
12455
|
try {
|
|
11569
12456
|
const prefix = (0, import_node_child_process4.execSync)("npm prefix -g", { timeout: 5e3, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
|
|
11570
|
-
candidates.push((0,
|
|
11571
|
-
candidates.push((0,
|
|
12457
|
+
candidates.push((0, import_node_path11.join)(prefix, "lib", "node_modules", "@agenticmail", "cli", "package.json"));
|
|
12458
|
+
candidates.push((0, import_node_path11.join)(prefix, "lib", "node_modules", "agenticmail", "package.json"));
|
|
11572
12459
|
} catch {
|
|
11573
12460
|
}
|
|
11574
12461
|
for (const p of candidates) {
|
|
11575
12462
|
try {
|
|
11576
|
-
if ((0,
|
|
11577
|
-
const pkg = JSON.parse((0,
|
|
12463
|
+
if ((0, import_node_fs9.existsSync)(p)) {
|
|
12464
|
+
const pkg = JSON.parse((0, import_node_fs9.readFileSync)(p, "utf-8"));
|
|
11578
12465
|
if (pkg.version) return pkg.version;
|
|
11579
12466
|
}
|
|
11580
12467
|
} catch {
|
|
@@ -11587,9 +12474,9 @@ var ServiceManager = class {
|
|
|
11587
12474
|
* This ensures AgenticMail doesn't fail on boot when Docker is still loading.
|
|
11588
12475
|
*/
|
|
11589
12476
|
generateStartScript(nodePath, apiEntry) {
|
|
11590
|
-
const scriptPath = (0,
|
|
11591
|
-
const scriptDir = (0,
|
|
11592
|
-
if (!(0,
|
|
12477
|
+
const scriptPath = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "bin", "start-server.sh");
|
|
12478
|
+
const scriptDir = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "bin");
|
|
12479
|
+
if (!(0, import_node_fs9.existsSync)(scriptDir)) (0, import_node_fs9.mkdirSync)(scriptDir, { recursive: true });
|
|
11593
12480
|
const script = [
|
|
11594
12481
|
"#!/bin/bash",
|
|
11595
12482
|
"# AgenticMail auto-start script",
|
|
@@ -11640,7 +12527,7 @@ var ServiceManager = class {
|
|
|
11640
12527
|
`log "Starting API server: ${nodePath} ${apiEntry}"`,
|
|
11641
12528
|
`exec "${nodePath}" "${apiEntry}"`
|
|
11642
12529
|
].join("\n") + "\n";
|
|
11643
|
-
(0,
|
|
12530
|
+
(0, import_node_fs9.writeFileSync)(scriptPath, script, { mode: 493 });
|
|
11644
12531
|
return scriptPath;
|
|
11645
12532
|
}
|
|
11646
12533
|
/**
|
|
@@ -11653,9 +12540,9 @@ var ServiceManager = class {
|
|
|
11653
12540
|
* - Service version tracking in env vars
|
|
11654
12541
|
*/
|
|
11655
12542
|
generatePlist(nodePath, apiEntry, configPath) {
|
|
11656
|
-
const config = JSON.parse((0,
|
|
11657
|
-
const logDir = (0,
|
|
11658
|
-
if (!(0,
|
|
12543
|
+
const config = JSON.parse((0, import_node_fs9.readFileSync)(configPath, "utf-8"));
|
|
12544
|
+
const logDir = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "logs");
|
|
12545
|
+
if (!(0, import_node_fs9.existsSync)(logDir)) (0, import_node_fs9.mkdirSync)(logDir, { recursive: true });
|
|
11659
12546
|
const version = this.getVersion();
|
|
11660
12547
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
11661
12548
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -11676,9 +12563,9 @@ var ServiceManager = class {
|
|
|
11676
12563
|
<key>EnvironmentVariables</key>
|
|
11677
12564
|
<dict>
|
|
11678
12565
|
<key>HOME</key>
|
|
11679
|
-
<string>${(0,
|
|
12566
|
+
<string>${(0, import_node_os9.homedir)()}</string>
|
|
11680
12567
|
<key>AGENTICMAIL_DATA_DIR</key>
|
|
11681
|
-
<string>${config.dataDir || (0,
|
|
12568
|
+
<string>${config.dataDir || (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail")}</string>
|
|
11682
12569
|
<key>PATH</key>
|
|
11683
12570
|
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
11684
12571
|
<key>AGENTICMAIL_SERVICE_VERSION</key>
|
|
@@ -11731,8 +12618,8 @@ var ServiceManager = class {
|
|
|
11731
12618
|
* - Proper dependency ordering
|
|
11732
12619
|
*/
|
|
11733
12620
|
generateSystemdUnit(nodePath, apiEntry, configPath) {
|
|
11734
|
-
const config = JSON.parse((0,
|
|
11735
|
-
const dataDir = config.dataDir || (0,
|
|
12621
|
+
const config = JSON.parse((0, import_node_fs9.readFileSync)(configPath, "utf-8"));
|
|
12622
|
+
const dataDir = config.dataDir || (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail");
|
|
11736
12623
|
const version = this.getVersion();
|
|
11737
12624
|
const startScript = this.generateStartScript(nodePath, apiEntry);
|
|
11738
12625
|
return `[Unit]
|
|
@@ -11749,7 +12636,7 @@ Restart=always
|
|
|
11749
12636
|
RestartSec=15
|
|
11750
12637
|
TimeoutStartSec=660
|
|
11751
12638
|
LimitNOFILE=8192
|
|
11752
|
-
Environment=HOME=${(0,
|
|
12639
|
+
Environment=HOME=${(0, import_node_os9.homedir)()}
|
|
11753
12640
|
Environment=AGENTICMAIL_DATA_DIR=${dataDir}
|
|
11754
12641
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin
|
|
11755
12642
|
Environment=AGENTICMAIL_SERVICE_VERSION=${version}
|
|
@@ -11762,8 +12649,8 @@ WantedBy=default.target
|
|
|
11762
12649
|
* Install the auto-start service.
|
|
11763
12650
|
*/
|
|
11764
12651
|
install() {
|
|
11765
|
-
const configPath = (0,
|
|
11766
|
-
if (!(0,
|
|
12652
|
+
const configPath = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "config.json");
|
|
12653
|
+
if (!(0, import_node_fs9.existsSync)(configPath)) {
|
|
11767
12654
|
return { installed: false, message: "Config not found. Run agenticmail setup first." };
|
|
11768
12655
|
}
|
|
11769
12656
|
const nodePath = this.getNodePath();
|
|
@@ -11775,17 +12662,17 @@ WantedBy=default.target
|
|
|
11775
12662
|
}
|
|
11776
12663
|
const servicePath = this.getServicePath();
|
|
11777
12664
|
if (this.os === "darwin") {
|
|
11778
|
-
const dir2 = (0,
|
|
11779
|
-
if (!(0,
|
|
11780
|
-
if ((0,
|
|
12665
|
+
const dir2 = (0, import_node_path11.join)((0, import_node_os9.homedir)(), "Library", "LaunchAgents");
|
|
12666
|
+
if (!(0, import_node_fs9.existsSync)(dir2)) (0, import_node_fs9.mkdirSync)(dir2, { recursive: true });
|
|
12667
|
+
if ((0, import_node_fs9.existsSync)(servicePath)) {
|
|
11781
12668
|
try {
|
|
11782
12669
|
(0, import_node_child_process4.execFileSync)("launchctl", ["unload", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
11783
12670
|
} catch {
|
|
11784
12671
|
}
|
|
11785
12672
|
}
|
|
11786
12673
|
const plist = this.generatePlist(nodePath, apiEntry, configPath);
|
|
11787
|
-
(0,
|
|
11788
|
-
(0,
|
|
12674
|
+
(0, import_node_fs9.writeFileSync)(servicePath, plist);
|
|
12675
|
+
(0, import_node_fs9.chmodSync)(servicePath, 384);
|
|
11789
12676
|
try {
|
|
11790
12677
|
(0, import_node_child_process4.execFileSync)("launchctl", ["load", servicePath], { timeout: 1e4, stdio: "ignore" });
|
|
11791
12678
|
} catch (err) {
|
|
@@ -11793,11 +12680,11 @@ WantedBy=default.target
|
|
|
11793
12680
|
}
|
|
11794
12681
|
return { installed: true, message: `Service installed at ${servicePath}` };
|
|
11795
12682
|
} else if (this.os === "linux") {
|
|
11796
|
-
const dir2 = (0,
|
|
11797
|
-
if (!(0,
|
|
12683
|
+
const dir2 = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".config", "systemd", "user");
|
|
12684
|
+
if (!(0, import_node_fs9.existsSync)(dir2)) (0, import_node_fs9.mkdirSync)(dir2, { recursive: true });
|
|
11798
12685
|
const unit = this.generateSystemdUnit(nodePath, apiEntry, configPath);
|
|
11799
|
-
(0,
|
|
11800
|
-
(0,
|
|
12686
|
+
(0, import_node_fs9.writeFileSync)(servicePath, unit);
|
|
12687
|
+
(0, import_node_fs9.chmodSync)(servicePath, 384);
|
|
11801
12688
|
try {
|
|
11802
12689
|
(0, import_node_child_process4.execFileSync)("systemctl", ["--user", "daemon-reload"], { timeout: 1e4, stdio: "ignore" });
|
|
11803
12690
|
(0, import_node_child_process4.execFileSync)("systemctl", ["--user", "enable", SYSTEMD_UNIT], { timeout: 1e4, stdio: "ignore" });
|
|
@@ -11819,7 +12706,7 @@ WantedBy=default.target
|
|
|
11819
12706
|
*/
|
|
11820
12707
|
uninstall() {
|
|
11821
12708
|
const servicePath = this.getServicePath();
|
|
11822
|
-
if (!(0,
|
|
12709
|
+
if (!(0, import_node_fs9.existsSync)(servicePath)) {
|
|
11823
12710
|
return { removed: false, message: "Service is not installed." };
|
|
11824
12711
|
}
|
|
11825
12712
|
if (this.os === "darwin") {
|
|
@@ -11828,7 +12715,7 @@ WantedBy=default.target
|
|
|
11828
12715
|
} catch {
|
|
11829
12716
|
}
|
|
11830
12717
|
try {
|
|
11831
|
-
(0,
|
|
12718
|
+
(0, import_node_fs9.unlinkSync)(servicePath);
|
|
11832
12719
|
} catch {
|
|
11833
12720
|
}
|
|
11834
12721
|
return { removed: true, message: "Service removed." };
|
|
@@ -11839,7 +12726,7 @@ WantedBy=default.target
|
|
|
11839
12726
|
} catch {
|
|
11840
12727
|
}
|
|
11841
12728
|
try {
|
|
11842
|
-
(0,
|
|
12729
|
+
(0, import_node_fs9.unlinkSync)(servicePath);
|
|
11843
12730
|
} catch {
|
|
11844
12731
|
}
|
|
11845
12732
|
try {
|
|
@@ -11857,7 +12744,7 @@ WantedBy=default.target
|
|
|
11857
12744
|
status() {
|
|
11858
12745
|
const servicePath = this.getServicePath();
|
|
11859
12746
|
const plat = this.os === "darwin" ? "launchd" : this.os === "linux" ? "systemd" : "unsupported";
|
|
11860
|
-
const installed = (0,
|
|
12747
|
+
const installed = (0, import_node_fs9.existsSync)(servicePath);
|
|
11861
12748
|
let running = false;
|
|
11862
12749
|
if (installed) {
|
|
11863
12750
|
if (this.os === "darwin") {
|
|
@@ -11912,34 +12799,34 @@ WantedBy=default.target
|
|
|
11912
12799
|
needsRepair() {
|
|
11913
12800
|
if (this.os !== "darwin" && this.os !== "linux") return null;
|
|
11914
12801
|
const servicePath = this.getServicePath();
|
|
11915
|
-
if (!(0,
|
|
12802
|
+
if (!(0, import_node_fs9.existsSync)(servicePath)) return null;
|
|
11916
12803
|
let serviceContent = "";
|
|
11917
12804
|
try {
|
|
11918
|
-
serviceContent = (0,
|
|
12805
|
+
serviceContent = (0, import_node_fs9.readFileSync)(servicePath, "utf-8");
|
|
11919
12806
|
} catch {
|
|
11920
12807
|
return { reason: "Service file unreadable" };
|
|
11921
12808
|
}
|
|
11922
|
-
const startScript = (0,
|
|
12809
|
+
const startScript = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "bin", "start-server.sh");
|
|
11923
12810
|
if (serviceContent.includes(startScript)) {
|
|
11924
|
-
if (!(0,
|
|
12811
|
+
if (!(0, import_node_fs9.existsSync)(startScript)) {
|
|
11925
12812
|
return { reason: "start-server.sh is missing" };
|
|
11926
12813
|
}
|
|
11927
12814
|
let scriptContent = "";
|
|
11928
12815
|
try {
|
|
11929
|
-
scriptContent = (0,
|
|
12816
|
+
scriptContent = (0, import_node_fs9.readFileSync)(startScript, "utf-8");
|
|
11930
12817
|
} catch {
|
|
11931
12818
|
return { reason: "start-server.sh unreadable" };
|
|
11932
12819
|
}
|
|
11933
12820
|
const apiPathMatch = scriptContent.match(/(\/[^"\s]+@agenticmail\/api\/dist\/index\.js)/);
|
|
11934
12821
|
if (apiPathMatch) {
|
|
11935
12822
|
const referenced = apiPathMatch[1];
|
|
11936
|
-
if (!(0,
|
|
12823
|
+
if (!(0, import_node_fs9.existsSync)(referenced)) {
|
|
11937
12824
|
return { reason: `start-server.sh references missing path: ${referenced}` };
|
|
11938
12825
|
}
|
|
11939
12826
|
}
|
|
11940
12827
|
if (/node_modules\/agenticmail\/(?!.*@agenticmail\/cli)/.test(scriptContent)) {
|
|
11941
12828
|
const stale = /(\S*node_modules\/agenticmail\/\S*)/.exec(scriptContent)?.[1];
|
|
11942
|
-
if (stale && !(0,
|
|
12829
|
+
if (stale && !(0, import_node_fs9.existsSync)(stale)) {
|
|
11943
12830
|
return { reason: `start-server.sh references legacy unscoped path: ${stale}` };
|
|
11944
12831
|
}
|
|
11945
12832
|
}
|
|
@@ -11952,11 +12839,11 @@ WantedBy=default.target
|
|
|
11952
12839
|
return { reason: `Service version drift (current CLI is v${currentVersion})` };
|
|
11953
12840
|
}
|
|
11954
12841
|
}
|
|
11955
|
-
const entryCache = (0,
|
|
11956
|
-
if ((0,
|
|
12842
|
+
const entryCache = (0, import_node_path11.join)((0, import_node_os9.homedir)(), ".agenticmail", "api-entry.path");
|
|
12843
|
+
if ((0, import_node_fs9.existsSync)(entryCache)) {
|
|
11957
12844
|
try {
|
|
11958
|
-
const cached = (0,
|
|
11959
|
-
if (cached && !(0,
|
|
12845
|
+
const cached = (0, import_node_fs9.readFileSync)(entryCache, "utf-8").trim();
|
|
12846
|
+
if (cached && !(0, import_node_fs9.existsSync)(cached)) {
|
|
11960
12847
|
return { reason: `Cached API entry path no longer exists: ${cached}` };
|
|
11961
12848
|
}
|
|
11962
12849
|
} catch {
|
|
@@ -12007,13 +12894,13 @@ var SetupManager = class {
|
|
|
12007
12894
|
* falls back to monorepo location.
|
|
12008
12895
|
*/
|
|
12009
12896
|
getComposePath() {
|
|
12010
|
-
const standalonePath = (0,
|
|
12011
|
-
if ((0,
|
|
12897
|
+
const standalonePath = (0, import_node_path12.join)((0, import_node_os10.homedir)(), ".agenticmail", "docker-compose.yml");
|
|
12898
|
+
if ((0, import_node_fs10.existsSync)(standalonePath)) return standalonePath;
|
|
12012
12899
|
const cwd = process.cwd();
|
|
12013
|
-
const candidates = [cwd, (0,
|
|
12900
|
+
const candidates = [cwd, (0, import_node_path12.join)(cwd, "..")];
|
|
12014
12901
|
for (const dir2 of candidates) {
|
|
12015
|
-
const p = (0,
|
|
12016
|
-
if ((0,
|
|
12902
|
+
const p = (0, import_node_path12.join)(dir2, "docker-compose.yml");
|
|
12903
|
+
if ((0, import_node_fs10.existsSync)(p)) return p;
|
|
12017
12904
|
}
|
|
12018
12905
|
return standalonePath;
|
|
12019
12906
|
}
|
|
@@ -12023,19 +12910,19 @@ var SetupManager = class {
|
|
|
12023
12910
|
* Always regenerates Docker files to keep passwords in sync.
|
|
12024
12911
|
*/
|
|
12025
12912
|
initConfig() {
|
|
12026
|
-
const dataDir = (0,
|
|
12027
|
-
const configPath = (0,
|
|
12028
|
-
const envPath = (0,
|
|
12029
|
-
if ((0,
|
|
12913
|
+
const dataDir = (0, import_node_path12.join)((0, import_node_os10.homedir)(), ".agenticmail");
|
|
12914
|
+
const configPath = (0, import_node_path12.join)(dataDir, "config.json");
|
|
12915
|
+
const envPath = (0, import_node_path12.join)(dataDir, ".env");
|
|
12916
|
+
if ((0, import_node_fs10.existsSync)(configPath)) {
|
|
12030
12917
|
try {
|
|
12031
|
-
const existing = JSON.parse((0,
|
|
12918
|
+
const existing = JSON.parse((0, import_node_fs10.readFileSync)(configPath, "utf-8"));
|
|
12032
12919
|
this.generateDockerFiles(existing);
|
|
12033
12920
|
return { configPath, envPath, config: existing, isNew: false };
|
|
12034
12921
|
} catch {
|
|
12035
12922
|
}
|
|
12036
12923
|
}
|
|
12037
|
-
if (!(0,
|
|
12038
|
-
(0,
|
|
12924
|
+
if (!(0, import_node_fs10.existsSync)(dataDir)) {
|
|
12925
|
+
(0, import_node_fs10.mkdirSync)(dataDir, { recursive: true });
|
|
12039
12926
|
}
|
|
12040
12927
|
const masterKey = `mk_${(0, import_node_crypto6.randomBytes)(24).toString("hex")}`;
|
|
12041
12928
|
const stalwartPassword = (0, import_node_crypto6.randomBytes)(16).toString("hex");
|
|
@@ -12051,8 +12938,8 @@ var SetupManager = class {
|
|
|
12051
12938
|
api: { port: 3829, host: "127.0.0.1" },
|
|
12052
12939
|
dataDir
|
|
12053
12940
|
};
|
|
12054
|
-
(0,
|
|
12055
|
-
(0,
|
|
12941
|
+
(0, import_node_fs10.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
12942
|
+
(0, import_node_fs10.chmodSync)(configPath, 384);
|
|
12056
12943
|
const envContent = `# Auto-generated by agenticmail setup
|
|
12057
12944
|
STALWART_ADMIN_USER=admin
|
|
12058
12945
|
STALWART_ADMIN_PASSWORD=${stalwartPassword}
|
|
@@ -12067,8 +12954,8 @@ SMTP_PORT=587
|
|
|
12067
12954
|
IMAP_HOST=localhost
|
|
12068
12955
|
IMAP_PORT=143
|
|
12069
12956
|
`;
|
|
12070
|
-
(0,
|
|
12071
|
-
(0,
|
|
12957
|
+
(0, import_node_fs10.writeFileSync)(envPath, envContent);
|
|
12958
|
+
(0, import_node_fs10.chmodSync)(envPath, 384);
|
|
12072
12959
|
this.generateDockerFiles(config);
|
|
12073
12960
|
return { configPath, envPath, config, isNew: true };
|
|
12074
12961
|
}
|
|
@@ -12077,13 +12964,13 @@ IMAP_PORT=143
|
|
|
12077
12964
|
* with the correct admin password from config.
|
|
12078
12965
|
*/
|
|
12079
12966
|
generateDockerFiles(config) {
|
|
12080
|
-
const dataDir = config.dataDir || (0,
|
|
12081
|
-
if (!(0,
|
|
12082
|
-
(0,
|
|
12967
|
+
const dataDir = config.dataDir || (0, import_node_path12.join)((0, import_node_os10.homedir)(), ".agenticmail");
|
|
12968
|
+
if (!(0, import_node_fs10.existsSync)(dataDir)) {
|
|
12969
|
+
(0, import_node_fs10.mkdirSync)(dataDir, { recursive: true });
|
|
12083
12970
|
}
|
|
12084
12971
|
const password = config.stalwart?.adminPassword || "changeme";
|
|
12085
|
-
const composePath = (0,
|
|
12086
|
-
(0,
|
|
12972
|
+
const composePath = (0, import_node_path12.join)(dataDir, "docker-compose.yml");
|
|
12973
|
+
(0, import_node_fs10.writeFileSync)(composePath, `services:
|
|
12087
12974
|
stalwart:
|
|
12088
12975
|
# Pinned to v0.15.5 \u2014 Stalwart 0.16+ moved its config to JSON
|
|
12089
12976
|
# at /etc/stalwart/config.json (hardcoded into the container
|
|
@@ -12112,9 +12999,9 @@ IMAP_PORT=143
|
|
|
12112
12999
|
volumes:
|
|
12113
13000
|
stalwart-data:
|
|
12114
13001
|
`);
|
|
12115
|
-
(0,
|
|
12116
|
-
const tomlPath = (0,
|
|
12117
|
-
(0,
|
|
13002
|
+
(0, import_node_fs10.chmodSync)(composePath, 384);
|
|
13003
|
+
const tomlPath = (0, import_node_path12.join)(dataDir, "stalwart.toml");
|
|
13004
|
+
(0, import_node_fs10.writeFileSync)(tomlPath, `# Stalwart Mail Server \u2014 AgenticMail Configuration
|
|
12118
13005
|
|
|
12119
13006
|
[server]
|
|
12120
13007
|
hostname = "localhost"
|
|
@@ -12164,27 +13051,27 @@ enable = true
|
|
|
12164
13051
|
user = "admin"
|
|
12165
13052
|
secret = "${password}"
|
|
12166
13053
|
`);
|
|
12167
|
-
(0,
|
|
13054
|
+
(0, import_node_fs10.chmodSync)(tomlPath, 384);
|
|
12168
13055
|
}
|
|
12169
13056
|
/**
|
|
12170
13057
|
* Check if config has already been initialized.
|
|
12171
13058
|
*/
|
|
12172
13059
|
isInitialized() {
|
|
12173
|
-
const configPath = (0,
|
|
12174
|
-
return (0,
|
|
13060
|
+
const configPath = (0, import_node_path12.join)((0, import_node_os10.homedir)(), ".agenticmail", "config.json");
|
|
13061
|
+
return (0, import_node_fs10.existsSync)(configPath);
|
|
12175
13062
|
}
|
|
12176
13063
|
};
|
|
12177
13064
|
|
|
12178
13065
|
// src/media/manager.ts
|
|
12179
13066
|
var import_node_child_process6 = require("child_process");
|
|
12180
13067
|
var import_node_util = require("util");
|
|
12181
|
-
var
|
|
12182
|
-
var
|
|
13068
|
+
var import_node_fs12 = require("fs");
|
|
13069
|
+
var import_node_path13 = require("path");
|
|
12183
13070
|
|
|
12184
13071
|
// src/media/binaries.ts
|
|
12185
13072
|
var import_node_child_process5 = require("child_process");
|
|
12186
|
-
var
|
|
12187
|
-
var
|
|
13073
|
+
var import_node_fs11 = require("fs");
|
|
13074
|
+
var import_meta4 = {};
|
|
12188
13075
|
var BINARY_SPECS = {
|
|
12189
13076
|
ffmpeg: {
|
|
12190
13077
|
binary: "ffmpeg",
|
|
@@ -12257,7 +13144,7 @@ function probeCommand(command, spec) {
|
|
|
12257
13144
|
}
|
|
12258
13145
|
function detectEdgeTts(spec) {
|
|
12259
13146
|
try {
|
|
12260
|
-
const resolved =
|
|
13147
|
+
const resolved = import_meta4.resolve?.("node-edge-tts");
|
|
12261
13148
|
if (resolved) {
|
|
12262
13149
|
return {
|
|
12263
13150
|
binary: "edge-tts",
|
|
@@ -12324,7 +13211,7 @@ function requireWhisperModel(modelPath) {
|
|
|
12324
13211
|
"A whisper.cpp model file is required (whisperModel option). Download one, e.g. ggml-base.en.bin, from https://huggingface.co/ggerganov/whisper.cpp and pass its absolute path."
|
|
12325
13212
|
);
|
|
12326
13213
|
}
|
|
12327
|
-
if (!(0,
|
|
13214
|
+
if (!(0, import_node_fs11.existsSync)(modelPath)) {
|
|
12328
13215
|
throw new Error(`whisper model file not found: ${modelPath}`);
|
|
12329
13216
|
}
|
|
12330
13217
|
return modelPath;
|
|
@@ -12376,7 +13263,7 @@ function validateInputPath(path2, label = "input") {
|
|
|
12376
13263
|
`${label} file path may not start with "-" \u2014 pass an absolute path so it cannot be parsed as a command flag`
|
|
12377
13264
|
);
|
|
12378
13265
|
}
|
|
12379
|
-
if (!(0,
|
|
13266
|
+
if (!(0, import_node_fs12.existsSync)(path2)) {
|
|
12380
13267
|
throw new Error(`${label} file not found: ${path2}`);
|
|
12381
13268
|
}
|
|
12382
13269
|
return path2;
|
|
@@ -12398,32 +13285,32 @@ var MediaManager = class {
|
|
|
12398
13285
|
if (options.outputDir) {
|
|
12399
13286
|
this.outputDir = options.outputDir;
|
|
12400
13287
|
} else if (options.dataDir) {
|
|
12401
|
-
this.outputDir = (0,
|
|
13288
|
+
this.outputDir = (0, import_node_path13.join)(options.dataDir, "media");
|
|
12402
13289
|
} else {
|
|
12403
13290
|
const tmp = process.env.TMPDIR || process.env.TEMP || "/tmp";
|
|
12404
|
-
this.outputDir = (0,
|
|
13291
|
+
this.outputDir = (0, import_node_path13.join)(tmp, "agenticmail-media");
|
|
12405
13292
|
}
|
|
12406
13293
|
}
|
|
12407
13294
|
/** Ensure the output directory exists; returns it. */
|
|
12408
13295
|
ensureOutputDir() {
|
|
12409
|
-
if (!(0,
|
|
12410
|
-
(0,
|
|
13296
|
+
if (!(0, import_node_fs12.existsSync)(this.outputDir)) {
|
|
13297
|
+
(0, import_node_fs12.mkdirSync)(this.outputDir, { recursive: true });
|
|
12411
13298
|
}
|
|
12412
13299
|
return this.outputDir;
|
|
12413
13300
|
}
|
|
12414
13301
|
/** Build an output path inside the managed output dir. */
|
|
12415
13302
|
outPath(prefix, ext) {
|
|
12416
|
-
return (0,
|
|
13303
|
+
return (0, import_node_path13.join)(this.ensureOutputDir(), `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1e6)}.${ext}`);
|
|
12417
13304
|
}
|
|
12418
13305
|
/** Build a sub-directory inside the managed output dir. */
|
|
12419
13306
|
outDir(prefix) {
|
|
12420
|
-
const dir2 = (0,
|
|
12421
|
-
(0,
|
|
13307
|
+
const dir2 = (0, import_node_path13.join)(this.ensureOutputDir(), `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1e6)}`);
|
|
13308
|
+
(0, import_node_fs12.mkdirSync)(dir2, { recursive: true });
|
|
12422
13309
|
return dir2;
|
|
12423
13310
|
}
|
|
12424
13311
|
/** Stat a produced file into a {@link MediaFileResult} envelope. */
|
|
12425
13312
|
fileResult(path2, extra = {}) {
|
|
12426
|
-
const stat = (0,
|
|
13313
|
+
const stat = (0, import_node_fs12.statSync)(path2);
|
|
12427
13314
|
return { ok: true, filePath: path2, sizeBytes: stat.size, ...extra };
|
|
12428
13315
|
}
|
|
12429
13316
|
// ─── binary invocation helpers (execFile, arg arrays, no shell) ────
|
|
@@ -12537,7 +13424,7 @@ var MediaManager = class {
|
|
|
12537
13424
|
/** Edit an image with ImageMagick. */
|
|
12538
13425
|
async imageEdit(opts) {
|
|
12539
13426
|
const input = validateInputPath(opts.input);
|
|
12540
|
-
const ext = safeExtension(opts.format, (0,
|
|
13427
|
+
const ext = safeExtension(opts.format, (0, import_node_path13.extname)(input).slice(1) || "png");
|
|
12541
13428
|
const out = this.outPath("img", ext);
|
|
12542
13429
|
switch (opts.action) {
|
|
12543
13430
|
case "resize": {
|
|
@@ -12624,7 +13511,7 @@ var MediaManager = class {
|
|
|
12624
13511
|
switch (opts.action) {
|
|
12625
13512
|
case "trim": {
|
|
12626
13513
|
const input = validateInputPath(opts.input);
|
|
12627
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13514
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp3"));
|
|
12628
13515
|
const a = ["-i", input];
|
|
12629
13516
|
if (opts.start) a.push("-ss", String(opts.start));
|
|
12630
13517
|
if (opts.end) a.push("-to", String(opts.end));
|
|
@@ -12645,8 +13532,8 @@ var MediaManager = class {
|
|
|
12645
13532
|
if (files.length < 2) throw new Error("At least 2 files are required for merge");
|
|
12646
13533
|
files.forEach((f, i) => validateInputPath(f, `files[${i}]`));
|
|
12647
13534
|
const listFile = this.outPath("concat", "txt");
|
|
12648
|
-
(0,
|
|
12649
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13535
|
+
(0, import_node_fs12.writeFileSync)(listFile, files.map((f) => `file '${f.replace(/'/g, "'\\''")}'`).join("\n"));
|
|
13536
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(files[0]).slice(1) || "mp3"));
|
|
12650
13537
|
try {
|
|
12651
13538
|
await this.ffmpeg(["-f", "concat", "-safe", "0", "-i", listFile, "-c", "copy", "-y", out]);
|
|
12652
13539
|
} finally {
|
|
@@ -12657,7 +13544,7 @@ var MediaManager = class {
|
|
|
12657
13544
|
case "volume": {
|
|
12658
13545
|
const input = validateInputPath(opts.input);
|
|
12659
13546
|
if (!opts.volume) throw new Error('volume is required (e.g. "1.5" or "10dB")');
|
|
12660
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13547
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp3"));
|
|
12661
13548
|
await this.ffmpeg(["-i", input, "-af", `volume=${opts.volume}`, "-y", out]);
|
|
12662
13549
|
return this.fileResult(out);
|
|
12663
13550
|
}
|
|
@@ -12665,7 +13552,7 @@ var MediaManager = class {
|
|
|
12665
13552
|
const input = validateInputPath(opts.input);
|
|
12666
13553
|
const factor = clampNumber(opts.speedFactor, 0.5, 100, 0);
|
|
12667
13554
|
if (!factor) throw new Error("speedFactor is required for speed");
|
|
12668
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13555
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp3"));
|
|
12669
13556
|
await this.ffmpeg(["-i", input, "-af", `atempo=${factor}`, "-y", out]);
|
|
12670
13557
|
return this.fileResult(out);
|
|
12671
13558
|
}
|
|
@@ -12677,7 +13564,7 @@ var MediaManager = class {
|
|
|
12677
13564
|
}
|
|
12678
13565
|
case "reverse": {
|
|
12679
13566
|
const input = validateInputPath(opts.input);
|
|
12680
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13567
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp3"));
|
|
12681
13568
|
await this.ffmpeg(["-i", input, "-af", "areverse", "-y", out]);
|
|
12682
13569
|
return this.fileResult(out);
|
|
12683
13570
|
}
|
|
@@ -12686,7 +13573,7 @@ var MediaManager = class {
|
|
|
12686
13573
|
const dur = clampNumber(opts.fadeDuration, 0.1, 3600, 3);
|
|
12687
13574
|
const probe = await this.ffprobe(input);
|
|
12688
13575
|
const totalDur = parseFloat(probe.format?.duration || "0");
|
|
12689
|
-
const out = this.outPath("aud", safeExtension(null, (0,
|
|
13576
|
+
const out = this.outPath("aud", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp3"));
|
|
12690
13577
|
let af;
|
|
12691
13578
|
if (opts.fadeType === "in") af = `afade=t=in:st=0:d=${dur}`;
|
|
12692
13579
|
else if (opts.fadeType === "out") af = `afade=t=out:st=${Math.max(0, totalDur - dur)}:d=${dur}`;
|
|
@@ -12716,7 +13603,7 @@ var MediaManager = class {
|
|
|
12716
13603
|
}));
|
|
12717
13604
|
return {
|
|
12718
13605
|
ok: true,
|
|
12719
|
-
file: (0,
|
|
13606
|
+
file: (0, import_node_path13.basename)(path2),
|
|
12720
13607
|
format: info.format?.format_long_name,
|
|
12721
13608
|
duration: info.format?.duration,
|
|
12722
13609
|
sizeBytes: parseInt(info.format?.size || "0", 10),
|
|
@@ -12729,7 +13616,7 @@ var MediaManager = class {
|
|
|
12729
13616
|
async videoEdit(opts) {
|
|
12730
13617
|
if (opts.action === "concatenate") return this.videoConcatenate(opts);
|
|
12731
13618
|
const input = validateInputPath(opts.input);
|
|
12732
|
-
const srcExt = safeExtension(null, (0,
|
|
13619
|
+
const srcExt = safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp4");
|
|
12733
13620
|
switch (opts.action) {
|
|
12734
13621
|
case "trim": {
|
|
12735
13622
|
const out = this.outPath("vid", srcExt);
|
|
@@ -12751,7 +13638,7 @@ var MediaManager = class {
|
|
|
12751
13638
|
const dir2 = this.outDir("frames");
|
|
12752
13639
|
const interval = clampNumber(opts.interval, 0.01, 3600, 1);
|
|
12753
13640
|
await this.ffmpeg(
|
|
12754
|
-
["-i", input, "-vf", `fps=1/${interval}`, (0,
|
|
13641
|
+
["-i", input, "-vf", `fps=1/${interval}`, (0, import_node_path13.join)(dir2, "frame-%04d.png"), "-y"],
|
|
12755
13642
|
TIMEOUT_LONG
|
|
12756
13643
|
);
|
|
12757
13644
|
return { ok: true, filePath: dir2, sizeBytes: 0, outputDir: dir2 };
|
|
@@ -12852,7 +13739,7 @@ var MediaManager = class {
|
|
|
12852
13739
|
}
|
|
12853
13740
|
}
|
|
12854
13741
|
async videoColorGrade(input, opts) {
|
|
12855
|
-
const out = this.outPath("vid", safeExtension(null, (0,
|
|
13742
|
+
const out = this.outPath("vid", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp4"));
|
|
12856
13743
|
let vf;
|
|
12857
13744
|
if (opts.lutPath) {
|
|
12858
13745
|
const lut = validateInputPath(opts.lutPath, "lutPath");
|
|
@@ -12910,7 +13797,7 @@ var MediaManager = class {
|
|
|
12910
13797
|
}
|
|
12911
13798
|
async videoTextOverlay(input, opts) {
|
|
12912
13799
|
if (!opts.text) throw new Error("text is required for text_overlay");
|
|
12913
|
-
const out = this.outPath("vid", safeExtension(null, (0,
|
|
13800
|
+
const out = this.outPath("vid", safeExtension(null, (0, import_node_path13.extname)(input).slice(1) || "mp4"));
|
|
12914
13801
|
const probeV = await this.ffprobe(input);
|
|
12915
13802
|
const vStream = (probeV.streams || []).find((s) => s.codec_type === "video");
|
|
12916
13803
|
const vw = vStream?.width || 1920;
|
|
@@ -13179,7 +14066,7 @@ var MediaManager = class {
|
|
|
13179
14066
|
files.forEach((f, i) => validateInputPath(f, `files[${i}]`));
|
|
13180
14067
|
const out = this.outPath("vid", "mp4");
|
|
13181
14068
|
const listFile = this.outPath("concat", "txt");
|
|
13182
|
-
(0,
|
|
14069
|
+
(0, import_node_fs12.writeFileSync)(listFile, files.map((f) => `file '${f.replace(/'/g, "'\\''")}'`).join("\n"));
|
|
13183
14070
|
try {
|
|
13184
14071
|
try {
|
|
13185
14072
|
await this.ffmpeg(["-f", "concat", "-safe", "0", "-i", listFile, "-c", "copy", "-y", out], TIMEOUT_LONG);
|
|
@@ -13308,9 +14195,9 @@ var MediaManager = class {
|
|
|
13308
14195
|
const bg = bgColors[i % bgColors.length];
|
|
13309
14196
|
const sizeMult = chunk.wc <= 2 ? 1.4 : chunk.wc <= 3 ? 1.1 : 1;
|
|
13310
14197
|
const fontSize = Math.round(baseFont * sizeMult);
|
|
13311
|
-
const txtPng = (0,
|
|
13312
|
-
const bgPng = (0,
|
|
13313
|
-
const finalPng = (0,
|
|
14198
|
+
const txtPng = (0, import_node_path13.join)(captionDir, `txt-${i}.png`);
|
|
14199
|
+
const bgPng = (0, import_node_path13.join)(captionDir, `bg-${i}.png`);
|
|
14200
|
+
const finalPng = (0, import_node_path13.join)(captionDir, `c-${String(i).padStart(4, "0")}.png`);
|
|
13314
14201
|
await this.magick([
|
|
13315
14202
|
"-size",
|
|
13316
14203
|
`${maxTextW}x`,
|
|
@@ -13434,12 +14321,12 @@ var MediaManager = class {
|
|
|
13434
14321
|
for (let i = 0; i < frameCount; i++) {
|
|
13435
14322
|
const t = i * interval;
|
|
13436
14323
|
if (t >= totalDur && totalDur > 0) break;
|
|
13437
|
-
const framePath = (0,
|
|
14324
|
+
const framePath = (0, import_node_path13.join)(frameDir, `frame-${String(i).padStart(3, "0")}.jpg`);
|
|
13438
14325
|
await this.ffmpeg(["-ss", String(t), "-i", input, "-frames:v", "1", "-q:v", "3", "-y", framePath], TIMEOUT_FAST);
|
|
13439
14326
|
frames.push({ time: t, path: framePath });
|
|
13440
14327
|
}
|
|
13441
14328
|
const transcript = [];
|
|
13442
|
-
if (opts.whisperModel && (0,
|
|
14329
|
+
if (opts.whisperModel && (0, import_node_fs12.existsSync)(opts.whisperModel) && detectBinary("whisper").available) {
|
|
13443
14330
|
const whisper = requireBinary("whisper");
|
|
13444
14331
|
const wavPath = this.outPath("understand-audio", "wav");
|
|
13445
14332
|
await this.ffmpeg(["-i", input, "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le", "-y", wavPath], TIMEOUT_FAST);
|
|
@@ -13475,7 +14362,7 @@ var MediaManager = class {
|
|
|
13475
14362
|
});
|
|
13476
14363
|
return {
|
|
13477
14364
|
ok: true,
|
|
13478
|
-
video: (0,
|
|
14365
|
+
video: (0, import_node_path13.basename)(input),
|
|
13479
14366
|
duration: totalDur,
|
|
13480
14367
|
resolution: rotation ? `${vH}x${vW} (rotated ${rotation})` : `${vW}x${vH}`,
|
|
13481
14368
|
totalFramesExtracted: frames.length,
|
|
@@ -13503,11 +14390,11 @@ var MediaManager = class {
|
|
|
13503
14390
|
if (!opts.refText || typeof opts.refText !== "string") {
|
|
13504
14391
|
throw new Error("refText is required for voice_clone (the transcript of the reference audio)");
|
|
13505
14392
|
}
|
|
13506
|
-
const pythonBin = opts.pythonBin && (0,
|
|
14393
|
+
const pythonBin = opts.pythonBin && (0, import_node_path13.isAbsolute)(opts.pythonBin) ? validateInputPath(opts.pythonBin, "pythonBin") : requireBinary("python");
|
|
13507
14394
|
const device = typeof opts.device === "string" && /^[a-z0-9]+$/i.test(opts.device) ? opts.device : "cpu";
|
|
13508
14395
|
const outWav = this.outPath("voiceclone", "wav");
|
|
13509
14396
|
const paramsFile = this.outPath("voiceclone-params", "json");
|
|
13510
|
-
(0,
|
|
14397
|
+
(0, import_node_fs12.writeFileSync)(paramsFile, JSON.stringify({
|
|
13511
14398
|
ref_file: refAudio,
|
|
13512
14399
|
ref_text: opts.refText,
|
|
13513
14400
|
gen_text: opts.text,
|
|
@@ -13548,7 +14435,7 @@ var MediaManager = class {
|
|
|
13548
14435
|
outOgg,
|
|
13549
14436
|
"-y"
|
|
13550
14437
|
]);
|
|
13551
|
-
if ((0,
|
|
14438
|
+
if ((0, import_node_fs12.existsSync)(outOgg)) return this.fileResult(outOgg, { format: "ogg" });
|
|
13552
14439
|
} catch {
|
|
13553
14440
|
}
|
|
13554
14441
|
}
|
|
@@ -13558,18 +14445,18 @@ var MediaManager = class {
|
|
|
13558
14445
|
/** Parse a whisper-produced SRT (located by stem) into timed segments. */
|
|
13559
14446
|
parseSrt(srtStem) {
|
|
13560
14447
|
let srtFile = `${srtStem}.srt`;
|
|
13561
|
-
if (!(0,
|
|
13562
|
-
const dir2 = (0,
|
|
13563
|
-
const stem2 = (0,
|
|
14448
|
+
if (!(0, import_node_fs12.existsSync)(srtFile)) {
|
|
14449
|
+
const dir2 = (0, import_node_path13.dirname)(srtStem);
|
|
14450
|
+
const stem2 = (0, import_node_path13.basename)(srtStem);
|
|
13564
14451
|
try {
|
|
13565
|
-
const candidates = (0,
|
|
13566
|
-
if (candidates.length > 0) srtFile = (0,
|
|
14452
|
+
const candidates = (0, import_node_fs12.readdirSync)(dir2).filter((f) => f.includes(stem2) && f.endsWith(".srt"));
|
|
14453
|
+
if (candidates.length > 0) srtFile = (0, import_node_path13.join)(dir2, candidates[0]);
|
|
13567
14454
|
} catch {
|
|
13568
14455
|
}
|
|
13569
14456
|
}
|
|
13570
|
-
if (!(0,
|
|
14457
|
+
if (!(0, import_node_fs12.existsSync)(srtFile)) return [];
|
|
13571
14458
|
const out = [];
|
|
13572
|
-
const content = (0,
|
|
14459
|
+
const content = (0, import_node_fs12.readFileSync)(srtFile, "utf8");
|
|
13573
14460
|
for (const block of content.trim().split(/\n\n+/)) {
|
|
13574
14461
|
const lines = block.trim().split("\n");
|
|
13575
14462
|
if (lines.length < 3) continue;
|
|
@@ -13585,7 +14472,7 @@ var MediaManager = class {
|
|
|
13585
14472
|
/** Unlink a file, swallowing any error (cleanup best-effort). */
|
|
13586
14473
|
tryUnlink(path2) {
|
|
13587
14474
|
try {
|
|
13588
|
-
(0,
|
|
14475
|
+
(0, import_node_fs12.unlinkSync)(path2);
|
|
13589
14476
|
} catch {
|
|
13590
14477
|
}
|
|
13591
14478
|
}
|
|
@@ -13593,10 +14480,10 @@ var MediaManager = class {
|
|
|
13593
14480
|
tryUnlinkSrt(srtStem) {
|
|
13594
14481
|
this.tryUnlink(`${srtStem}.srt`);
|
|
13595
14482
|
try {
|
|
13596
|
-
const dir2 = (0,
|
|
13597
|
-
const stem2 = (0,
|
|
13598
|
-
for (const f of (0,
|
|
13599
|
-
if (f.includes(stem2) && f.endsWith(".srt")) this.tryUnlink((0,
|
|
14483
|
+
const dir2 = (0, import_node_path13.dirname)(srtStem);
|
|
14484
|
+
const stem2 = (0, import_node_path13.basename)(srtStem);
|
|
14485
|
+
for (const f of (0, import_node_fs12.readdirSync)(dir2)) {
|
|
14486
|
+
if (f.includes(stem2) && f.endsWith(".srt")) this.tryUnlink((0, import_node_path13.join)(dir2, f));
|
|
13600
14487
|
}
|
|
13601
14488
|
} catch {
|
|
13602
14489
|
}
|
|
@@ -13604,7 +14491,7 @@ var MediaManager = class {
|
|
|
13604
14491
|
/** Recursively remove a directory, swallowing errors. */
|
|
13605
14492
|
tryRmDir(dir2) {
|
|
13606
14493
|
try {
|
|
13607
|
-
(0,
|
|
14494
|
+
(0, import_node_fs12.rmSync)(dir2, { recursive: true, force: true });
|
|
13608
14495
|
} catch {
|
|
13609
14496
|
}
|
|
13610
14497
|
}
|
|
@@ -13644,10 +14531,10 @@ function threadIdFor(input) {
|
|
|
13644
14531
|
}
|
|
13645
14532
|
|
|
13646
14533
|
// src/threading/thread-cache.ts
|
|
13647
|
-
var
|
|
13648
|
-
var
|
|
13649
|
-
var
|
|
13650
|
-
var CACHE_DIR_DEFAULT = (0,
|
|
14534
|
+
var import_node_fs13 = require("fs");
|
|
14535
|
+
var import_node_os11 = require("os");
|
|
14536
|
+
var import_node_path14 = require("path");
|
|
14537
|
+
var CACHE_DIR_DEFAULT = (0, import_node_path14.join)((0, import_node_os11.homedir)(), ".agenticmail", "thread-cache");
|
|
13651
14538
|
var DEFAULT_K_MESSAGES = 10;
|
|
13652
14539
|
var DEFAULT_LRU_CAP = 5e3;
|
|
13653
14540
|
var PREVIEW_MAX_CHARS = 240;
|
|
@@ -13660,22 +14547,22 @@ var ThreadCache = class {
|
|
|
13660
14547
|
this.k = opts.k ?? DEFAULT_K_MESSAGES;
|
|
13661
14548
|
this.lruCap = opts.lruCap ?? DEFAULT_LRU_CAP;
|
|
13662
14549
|
try {
|
|
13663
|
-
(0,
|
|
14550
|
+
(0, import_node_fs13.mkdirSync)(this.dir, { recursive: true });
|
|
13664
14551
|
} catch {
|
|
13665
14552
|
}
|
|
13666
14553
|
}
|
|
13667
14554
|
pathFor(threadId) {
|
|
13668
|
-
return (0,
|
|
14555
|
+
return (0, import_node_path14.join)(this.dir, `${threadId}.json`);
|
|
13669
14556
|
}
|
|
13670
14557
|
read(threadId) {
|
|
13671
14558
|
const p = this.pathFor(threadId);
|
|
13672
|
-
if (!(0,
|
|
14559
|
+
if (!(0, import_node_fs13.existsSync)(p)) return null;
|
|
13673
14560
|
try {
|
|
13674
|
-
const raw = (0,
|
|
14561
|
+
const raw = (0, import_node_fs13.readFileSync)(p, "utf-8");
|
|
13675
14562
|
return JSON.parse(raw);
|
|
13676
14563
|
} catch {
|
|
13677
14564
|
try {
|
|
13678
|
-
(0,
|
|
14565
|
+
(0, import_node_fs13.rmSync)(p, { force: true });
|
|
13679
14566
|
} catch {
|
|
13680
14567
|
}
|
|
13681
14568
|
return null;
|
|
@@ -13716,7 +14603,7 @@ var ThreadCache = class {
|
|
|
13716
14603
|
/** Permanently remove a thread's cache (called on [FINAL] / [DONE] / [CLOSED] / [WRAP]). */
|
|
13717
14604
|
delete(threadId) {
|
|
13718
14605
|
try {
|
|
13719
|
-
(0,
|
|
14606
|
+
(0, import_node_fs13.rmSync)(this.pathFor(threadId), { force: true });
|
|
13720
14607
|
} catch {
|
|
13721
14608
|
}
|
|
13722
14609
|
}
|
|
@@ -13736,8 +14623,8 @@ var ThreadCache = class {
|
|
|
13736
14623
|
writeAtomic(threadId, entry) {
|
|
13737
14624
|
const p = this.pathFor(threadId);
|
|
13738
14625
|
const tmp = `${p}.tmp`;
|
|
13739
|
-
(0,
|
|
13740
|
-
(0,
|
|
14626
|
+
(0, import_node_fs13.writeFileSync)(tmp, JSON.stringify(entry), "utf-8");
|
|
14627
|
+
(0, import_node_fs13.renameSync)(tmp, p);
|
|
13741
14628
|
}
|
|
13742
14629
|
/**
|
|
13743
14630
|
* Best-effort LRU eviction. Runs at most every 256 writes (we
|
|
@@ -13749,15 +14636,15 @@ var ThreadCache = class {
|
|
|
13749
14636
|
if (Math.random() > 1 / 256) return;
|
|
13750
14637
|
let files;
|
|
13751
14638
|
try {
|
|
13752
|
-
files = (0,
|
|
14639
|
+
files = (0, import_node_fs13.readdirSync)(this.dir).filter((f) => f.endsWith(".json"));
|
|
13753
14640
|
} catch {
|
|
13754
14641
|
return;
|
|
13755
14642
|
}
|
|
13756
14643
|
if (files.length <= this.lruCap) return;
|
|
13757
14644
|
const stats = files.map((f) => {
|
|
13758
|
-
const p = (0,
|
|
14645
|
+
const p = (0, import_node_path14.join)(this.dir, f);
|
|
13759
14646
|
try {
|
|
13760
|
-
return { p, mtime: (0,
|
|
14647
|
+
return { p, mtime: (0, import_node_fs13.statSync)(p).mtimeMs };
|
|
13761
14648
|
} catch {
|
|
13762
14649
|
return { p, mtime: 0 };
|
|
13763
14650
|
}
|
|
@@ -13766,7 +14653,7 @@ var ThreadCache = class {
|
|
|
13766
14653
|
const dropCount = Math.max(1, Math.floor(this.lruCap * 0.1));
|
|
13767
14654
|
for (let i = 0; i < dropCount; i++) {
|
|
13768
14655
|
try {
|
|
13769
|
-
(0,
|
|
14656
|
+
(0, import_node_fs13.rmSync)(stats[i].p, { force: true });
|
|
13770
14657
|
} catch {
|
|
13771
14658
|
}
|
|
13772
14659
|
}
|
|
@@ -13785,30 +14672,30 @@ function dedupAndCap(messages, k) {
|
|
|
13785
14672
|
}
|
|
13786
14673
|
|
|
13787
14674
|
// src/threading/agent-memory.ts
|
|
13788
|
-
var
|
|
13789
|
-
var
|
|
13790
|
-
var
|
|
13791
|
-
var MEMORY_DIR_DEFAULT = (0,
|
|
14675
|
+
var import_node_fs14 = require("fs");
|
|
14676
|
+
var import_node_os12 = require("os");
|
|
14677
|
+
var import_node_path15 = require("path");
|
|
14678
|
+
var MEMORY_DIR_DEFAULT = (0, import_node_path15.join)((0, import_node_os12.homedir)(), ".agenticmail", "agent-memory");
|
|
13792
14679
|
var AgentMemoryStore = class {
|
|
13793
14680
|
dir;
|
|
13794
14681
|
constructor(opts = {}) {
|
|
13795
14682
|
this.dir = opts.memoryDir ?? MEMORY_DIR_DEFAULT;
|
|
13796
14683
|
try {
|
|
13797
|
-
(0,
|
|
14684
|
+
(0, import_node_fs14.mkdirSync)(this.dir, { recursive: true });
|
|
13798
14685
|
} catch {
|
|
13799
14686
|
}
|
|
13800
14687
|
}
|
|
13801
14688
|
dirFor(agentId) {
|
|
13802
|
-
return (0,
|
|
14689
|
+
return (0, import_node_path15.join)(this.dir, sanitizeId(agentId));
|
|
13803
14690
|
}
|
|
13804
14691
|
pathFor(agentId, threadId) {
|
|
13805
|
-
return (0,
|
|
14692
|
+
return (0, import_node_path15.join)(this.dirFor(agentId), `${sanitizeId(threadId)}.md`);
|
|
13806
14693
|
}
|
|
13807
14694
|
read(agentId, threadId) {
|
|
13808
14695
|
const p = this.pathFor(agentId, threadId);
|
|
13809
|
-
if (!(0,
|
|
14696
|
+
if (!(0, import_node_fs14.existsSync)(p)) return null;
|
|
13810
14697
|
try {
|
|
13811
|
-
const raw = (0,
|
|
14698
|
+
const raw = (0, import_node_fs14.readFileSync)(p, "utf-8");
|
|
13812
14699
|
const parsed = parse(raw);
|
|
13813
14700
|
return { ...parsed, raw };
|
|
13814
14701
|
} catch {
|
|
@@ -13818,18 +14705,18 @@ var AgentMemoryStore = class {
|
|
|
13818
14705
|
write(agentId, threadId, fields) {
|
|
13819
14706
|
const agentDir = this.dirFor(agentId);
|
|
13820
14707
|
try {
|
|
13821
|
-
(0,
|
|
14708
|
+
(0, import_node_fs14.mkdirSync)(agentDir, { recursive: true });
|
|
13822
14709
|
} catch {
|
|
13823
14710
|
}
|
|
13824
14711
|
const body = render({ ...fields, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
13825
14712
|
const p = this.pathFor(agentId, threadId);
|
|
13826
14713
|
const tmp = `${p}.tmp`;
|
|
13827
|
-
(0,
|
|
13828
|
-
(0,
|
|
14714
|
+
(0, import_node_fs14.writeFileSync)(tmp, body, "utf-8");
|
|
14715
|
+
(0, import_node_fs14.renameSync)(tmp, p);
|
|
13829
14716
|
}
|
|
13830
14717
|
delete(agentId, threadId) {
|
|
13831
14718
|
try {
|
|
13832
|
-
(0,
|
|
14719
|
+
(0, import_node_fs14.rmSync)(this.pathFor(agentId, threadId), { force: true });
|
|
13833
14720
|
} catch {
|
|
13834
14721
|
}
|
|
13835
14722
|
}
|
|
@@ -13887,400 +14774,7 @@ function parse(raw) {
|
|
|
13887
14774
|
|
|
13888
14775
|
// src/memory/manager.ts
|
|
13889
14776
|
var import_node_crypto8 = require("crypto");
|
|
13890
|
-
|
|
13891
|
-
// src/memory/text-search.ts
|
|
13892
|
-
var BM25_K1 = 1.2;
|
|
13893
|
-
var BM25_B = 0.75;
|
|
13894
|
-
var FIELD_WEIGHT_TITLE = 3;
|
|
13895
|
-
var FIELD_WEIGHT_TAGS = 2;
|
|
13896
|
-
var FIELD_WEIGHT_CONTENT = 1;
|
|
13897
|
-
var PREFIX_MATCH_PENALTY = 0.7;
|
|
13898
|
-
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
13899
|
-
"a",
|
|
13900
|
-
"about",
|
|
13901
|
-
"above",
|
|
13902
|
-
"after",
|
|
13903
|
-
"again",
|
|
13904
|
-
"against",
|
|
13905
|
-
"all",
|
|
13906
|
-
"am",
|
|
13907
|
-
"an",
|
|
13908
|
-
"and",
|
|
13909
|
-
"any",
|
|
13910
|
-
"are",
|
|
13911
|
-
"as",
|
|
13912
|
-
"at",
|
|
13913
|
-
"be",
|
|
13914
|
-
"because",
|
|
13915
|
-
"been",
|
|
13916
|
-
"before",
|
|
13917
|
-
"being",
|
|
13918
|
-
"below",
|
|
13919
|
-
"between",
|
|
13920
|
-
"both",
|
|
13921
|
-
"but",
|
|
13922
|
-
"by",
|
|
13923
|
-
"can",
|
|
13924
|
-
"could",
|
|
13925
|
-
"did",
|
|
13926
|
-
"do",
|
|
13927
|
-
"does",
|
|
13928
|
-
"doing",
|
|
13929
|
-
"down",
|
|
13930
|
-
"during",
|
|
13931
|
-
"each",
|
|
13932
|
-
"either",
|
|
13933
|
-
"every",
|
|
13934
|
-
"few",
|
|
13935
|
-
"for",
|
|
13936
|
-
"from",
|
|
13937
|
-
"further",
|
|
13938
|
-
"get",
|
|
13939
|
-
"got",
|
|
13940
|
-
"had",
|
|
13941
|
-
"has",
|
|
13942
|
-
"have",
|
|
13943
|
-
"having",
|
|
13944
|
-
"he",
|
|
13945
|
-
"her",
|
|
13946
|
-
"here",
|
|
13947
|
-
"hers",
|
|
13948
|
-
"herself",
|
|
13949
|
-
"him",
|
|
13950
|
-
"himself",
|
|
13951
|
-
"his",
|
|
13952
|
-
"how",
|
|
13953
|
-
"i",
|
|
13954
|
-
"if",
|
|
13955
|
-
"in",
|
|
13956
|
-
"into",
|
|
13957
|
-
"is",
|
|
13958
|
-
"it",
|
|
13959
|
-
"its",
|
|
13960
|
-
"itself",
|
|
13961
|
-
"just",
|
|
13962
|
-
"may",
|
|
13963
|
-
"me",
|
|
13964
|
-
"might",
|
|
13965
|
-
"more",
|
|
13966
|
-
"most",
|
|
13967
|
-
"must",
|
|
13968
|
-
"my",
|
|
13969
|
-
"myself",
|
|
13970
|
-
"neither",
|
|
13971
|
-
"no",
|
|
13972
|
-
"nor",
|
|
13973
|
-
"not",
|
|
13974
|
-
"now",
|
|
13975
|
-
"of",
|
|
13976
|
-
"off",
|
|
13977
|
-
"on",
|
|
13978
|
-
"once",
|
|
13979
|
-
"only",
|
|
13980
|
-
"or",
|
|
13981
|
-
"other",
|
|
13982
|
-
"ought",
|
|
13983
|
-
"our",
|
|
13984
|
-
"ours",
|
|
13985
|
-
"ourselves",
|
|
13986
|
-
"out",
|
|
13987
|
-
"over",
|
|
13988
|
-
"own",
|
|
13989
|
-
"same",
|
|
13990
|
-
"shall",
|
|
13991
|
-
"she",
|
|
13992
|
-
"should",
|
|
13993
|
-
"so",
|
|
13994
|
-
"some",
|
|
13995
|
-
"such",
|
|
13996
|
-
"than",
|
|
13997
|
-
"that",
|
|
13998
|
-
"the",
|
|
13999
|
-
"their",
|
|
14000
|
-
"theirs",
|
|
14001
|
-
"them",
|
|
14002
|
-
"themselves",
|
|
14003
|
-
"then",
|
|
14004
|
-
"there",
|
|
14005
|
-
"these",
|
|
14006
|
-
"they",
|
|
14007
|
-
"this",
|
|
14008
|
-
"those",
|
|
14009
|
-
"through",
|
|
14010
|
-
"to",
|
|
14011
|
-
"too",
|
|
14012
|
-
"under",
|
|
14013
|
-
"until",
|
|
14014
|
-
"up",
|
|
14015
|
-
"us",
|
|
14016
|
-
"very",
|
|
14017
|
-
"was",
|
|
14018
|
-
"we",
|
|
14019
|
-
"were",
|
|
14020
|
-
"what",
|
|
14021
|
-
"when",
|
|
14022
|
-
"where",
|
|
14023
|
-
"which",
|
|
14024
|
-
"while",
|
|
14025
|
-
"who",
|
|
14026
|
-
"whom",
|
|
14027
|
-
"why",
|
|
14028
|
-
"will",
|
|
14029
|
-
"with",
|
|
14030
|
-
"would",
|
|
14031
|
-
"yet",
|
|
14032
|
-
"you",
|
|
14033
|
-
"your",
|
|
14034
|
-
"yours",
|
|
14035
|
-
"yourself",
|
|
14036
|
-
"yourselves"
|
|
14037
|
-
]);
|
|
14038
|
-
var STEM_RULES = [
|
|
14039
|
-
// Step 1: plurals and past participles
|
|
14040
|
-
[/ies$/, "i", 3],
|
|
14041
|
-
// policies → polici,eries → eri
|
|
14042
|
-
[/sses$/, "ss", 4],
|
|
14043
|
-
// addresses → address
|
|
14044
|
-
[/([^s])s$/, "$1", 3],
|
|
14045
|
-
// items → item, but not "ss"
|
|
14046
|
-
[/eed$/, "ee", 4],
|
|
14047
|
-
// agreed → agree
|
|
14048
|
-
[/ed$/, "", 3],
|
|
14049
|
-
// configured → configur, but min length 3
|
|
14050
|
-
[/ing$/, "", 4],
|
|
14051
|
-
// running → runn → run (handled below)
|
|
14052
|
-
// Step 2: derivational suffixes
|
|
14053
|
-
[/ational$/, "ate", 6],
|
|
14054
|
-
// relational → relate
|
|
14055
|
-
[/tion$/, "t", 5],
|
|
14056
|
-
// adoption → adopt
|
|
14057
|
-
[/ness$/, "", 5],
|
|
14058
|
-
// awareness → aware
|
|
14059
|
-
[/ment$/, "", 5],
|
|
14060
|
-
// deployment → deploy
|
|
14061
|
-
[/able$/, "", 5],
|
|
14062
|
-
// configurable → configur
|
|
14063
|
-
[/ible$/, "", 5],
|
|
14064
|
-
// accessible → access
|
|
14065
|
-
[/ful$/, "", 5],
|
|
14066
|
-
// powerful → power
|
|
14067
|
-
[/ous$/, "", 5],
|
|
14068
|
-
// dangerous → danger
|
|
14069
|
-
[/ive$/, "", 5],
|
|
14070
|
-
// interactive → interact
|
|
14071
|
-
[/ize$/, "", 4],
|
|
14072
|
-
// normalize → normal
|
|
14073
|
-
[/ise$/, "", 4],
|
|
14074
|
-
// organise → organ
|
|
14075
|
-
[/ally$/, "", 5],
|
|
14076
|
-
// automatically → automat
|
|
14077
|
-
[/ly$/, "", 4],
|
|
14078
|
-
// quickly → quick
|
|
14079
|
-
[/er$/, "", 4]
|
|
14080
|
-
// handler → handl
|
|
14081
|
-
];
|
|
14082
|
-
var DOUBLE_CONSONANT = /([^aeiou])\1$/;
|
|
14083
|
-
function stem(word) {
|
|
14084
|
-
if (word.length < 3) return word;
|
|
14085
|
-
let stemmed = word;
|
|
14086
|
-
for (const [pattern, replacement, minLen] of STEM_RULES) {
|
|
14087
|
-
if (stemmed.length >= minLen && pattern.test(stemmed)) {
|
|
14088
|
-
stemmed = stemmed.replace(pattern, replacement);
|
|
14089
|
-
break;
|
|
14090
|
-
}
|
|
14091
|
-
}
|
|
14092
|
-
if (stemmed.length > 2 && DOUBLE_CONSONANT.test(stemmed)) {
|
|
14093
|
-
stemmed = stemmed.slice(0, -1);
|
|
14094
|
-
}
|
|
14095
|
-
return stemmed;
|
|
14096
|
-
}
|
|
14097
|
-
function tokenize(text) {
|
|
14098
|
-
return text.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 1 && !STOP_WORDS.has(t)).map(stem);
|
|
14099
|
-
}
|
|
14100
|
-
var MemorySearchIndex = class {
|
|
14101
|
-
/** Posting lists: stemmed term → Set of memory IDs containing it */
|
|
14102
|
-
postings = /* @__PURE__ */ new Map();
|
|
14103
|
-
/** Per-document metadata for BM25 scoring */
|
|
14104
|
-
docs = /* @__PURE__ */ new Map();
|
|
14105
|
-
/** Pre-computed IDF values. Stale flag triggers lazy recomputation. */
|
|
14106
|
-
idf = /* @__PURE__ */ new Map();
|
|
14107
|
-
idfStale = true;
|
|
14108
|
-
/** 3-character prefix map for prefix matching: prefix → Set of full stems */
|
|
14109
|
-
prefixMap = /* @__PURE__ */ new Map();
|
|
14110
|
-
/** Total weighted document length (for computing average) */
|
|
14111
|
-
totalWeightedLen = 0;
|
|
14112
|
-
get docCount() {
|
|
14113
|
-
return this.docs.size;
|
|
14114
|
-
}
|
|
14115
|
-
get avgDocLen() {
|
|
14116
|
-
return this.docs.size > 0 ? this.totalWeightedLen / this.docs.size : 1;
|
|
14117
|
-
}
|
|
14118
|
-
/**
|
|
14119
|
-
* Index a memory entry. Extracts stems from title, content, and tags
|
|
14120
|
-
* with field-specific weighting and builds posting lists.
|
|
14121
|
-
*/
|
|
14122
|
-
addDocument(id, entry) {
|
|
14123
|
-
if (this.docs.has(id)) this.removeDocument(id);
|
|
14124
|
-
const titleTokens = tokenize(entry.title);
|
|
14125
|
-
const contentTokens = tokenize(entry.content);
|
|
14126
|
-
const tagTokens = entry.tags.flatMap((t) => tokenize(t));
|
|
14127
|
-
const weightedTf = /* @__PURE__ */ new Map();
|
|
14128
|
-
for (const t of titleTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_TITLE);
|
|
14129
|
-
for (const t of tagTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_TAGS);
|
|
14130
|
-
for (const t of contentTokens) weightedTf.set(t, (weightedTf.get(t) || 0) + FIELD_WEIGHT_CONTENT);
|
|
14131
|
-
const weightedLen = titleTokens.length * FIELD_WEIGHT_TITLE + tagTokens.length * FIELD_WEIGHT_TAGS + contentTokens.length * FIELD_WEIGHT_CONTENT;
|
|
14132
|
-
const allStems = /* @__PURE__ */ new Set();
|
|
14133
|
-
for (const t of weightedTf.keys()) allStems.add(t);
|
|
14134
|
-
const stemSequence = [...titleTokens, ...contentTokens];
|
|
14135
|
-
const docRecord = { weightedTf, weightedLen, allStems, stemSequence };
|
|
14136
|
-
this.docs.set(id, docRecord);
|
|
14137
|
-
this.totalWeightedLen += weightedLen;
|
|
14138
|
-
for (const term of allStems) {
|
|
14139
|
-
let posting = this.postings.get(term);
|
|
14140
|
-
if (!posting) {
|
|
14141
|
-
posting = /* @__PURE__ */ new Set();
|
|
14142
|
-
this.postings.set(term, posting);
|
|
14143
|
-
}
|
|
14144
|
-
posting.add(id);
|
|
14145
|
-
if (term.length >= 3) {
|
|
14146
|
-
const prefix = term.slice(0, 3);
|
|
14147
|
-
let prefixSet = this.prefixMap.get(prefix);
|
|
14148
|
-
if (!prefixSet) {
|
|
14149
|
-
prefixSet = /* @__PURE__ */ new Set();
|
|
14150
|
-
this.prefixMap.set(prefix, prefixSet);
|
|
14151
|
-
}
|
|
14152
|
-
prefixSet.add(term);
|
|
14153
|
-
}
|
|
14154
|
-
}
|
|
14155
|
-
this.idfStale = true;
|
|
14156
|
-
}
|
|
14157
|
-
/** Remove a document from the index. */
|
|
14158
|
-
removeDocument(id) {
|
|
14159
|
-
const doc = this.docs.get(id);
|
|
14160
|
-
if (!doc) return;
|
|
14161
|
-
this.totalWeightedLen -= doc.weightedLen;
|
|
14162
|
-
this.docs.delete(id);
|
|
14163
|
-
for (const term of doc.allStems) {
|
|
14164
|
-
const posting = this.postings.get(term);
|
|
14165
|
-
if (posting) {
|
|
14166
|
-
posting.delete(id);
|
|
14167
|
-
if (posting.size === 0) {
|
|
14168
|
-
this.postings.delete(term);
|
|
14169
|
-
if (term.length >= 3) {
|
|
14170
|
-
const prefixSet = this.prefixMap.get(term.slice(0, 3));
|
|
14171
|
-
if (prefixSet) {
|
|
14172
|
-
prefixSet.delete(term);
|
|
14173
|
-
if (prefixSet.size === 0) this.prefixMap.delete(term.slice(0, 3));
|
|
14174
|
-
}
|
|
14175
|
-
}
|
|
14176
|
-
}
|
|
14177
|
-
}
|
|
14178
|
-
}
|
|
14179
|
-
this.idfStale = true;
|
|
14180
|
-
}
|
|
14181
|
-
/** Recompute IDF values for all terms. Called lazily before search. */
|
|
14182
|
-
refreshIdf() {
|
|
14183
|
-
if (!this.idfStale) return;
|
|
14184
|
-
const N = this.docs.size;
|
|
14185
|
-
this.idf.clear();
|
|
14186
|
-
for (const [term, posting] of this.postings) {
|
|
14187
|
-
const df = posting.size;
|
|
14188
|
-
this.idf.set(term, Math.log((N - df + 0.5) / (df + 0.5) + 1));
|
|
14189
|
-
}
|
|
14190
|
-
this.idfStale = false;
|
|
14191
|
-
}
|
|
14192
|
-
/**
|
|
14193
|
-
* Expand query terms with prefix matches.
|
|
14194
|
-
* "deploy" → ["deploy", "deployment", "deploying", ...] (if they exist in the index)
|
|
14195
|
-
*/
|
|
14196
|
-
expandQueryTerms(queryStems) {
|
|
14197
|
-
const expanded = /* @__PURE__ */ new Map();
|
|
14198
|
-
for (const qs of queryStems) {
|
|
14199
|
-
if (this.postings.has(qs)) {
|
|
14200
|
-
expanded.set(qs, Math.max(expanded.get(qs) || 0, 1));
|
|
14201
|
-
}
|
|
14202
|
-
if (qs.length >= 3) {
|
|
14203
|
-
const prefix = qs.slice(0, 3);
|
|
14204
|
-
const candidates = this.prefixMap.get(prefix);
|
|
14205
|
-
if (candidates) {
|
|
14206
|
-
for (const candidate of candidates) {
|
|
14207
|
-
if (candidate !== qs && candidate.startsWith(qs)) {
|
|
14208
|
-
expanded.set(candidate, Math.max(expanded.get(candidate) || 0, PREFIX_MATCH_PENALTY));
|
|
14209
|
-
}
|
|
14210
|
-
}
|
|
14211
|
-
}
|
|
14212
|
-
}
|
|
14213
|
-
}
|
|
14214
|
-
return expanded;
|
|
14215
|
-
}
|
|
14216
|
-
/**
|
|
14217
|
-
* Compute bigram proximity boost: if two query terms appear adjacent
|
|
14218
|
-
* in the document's stem sequence, boost the score.
|
|
14219
|
-
*/
|
|
14220
|
-
bigramProximityBoost(docId, queryStems) {
|
|
14221
|
-
if (queryStems.length < 2) return 0;
|
|
14222
|
-
const doc = this.docs.get(docId);
|
|
14223
|
-
if (!doc || doc.stemSequence.length < 2) return 0;
|
|
14224
|
-
let boost = 0;
|
|
14225
|
-
const seq = doc.stemSequence;
|
|
14226
|
-
const querySet = new Set(queryStems);
|
|
14227
|
-
for (let i = 0; i < seq.length - 1; i++) {
|
|
14228
|
-
if (querySet.has(seq[i]) && querySet.has(seq[i + 1]) && seq[i] !== seq[i + 1]) {
|
|
14229
|
-
boost += 0.5;
|
|
14230
|
-
}
|
|
14231
|
-
}
|
|
14232
|
-
return Math.min(boost, 2);
|
|
14233
|
-
}
|
|
14234
|
-
/**
|
|
14235
|
-
* Search the index for documents matching a query.
|
|
14236
|
-
* Returns scored results sorted by BM25F relevance.
|
|
14237
|
-
*
|
|
14238
|
-
* @param query - Raw query string
|
|
14239
|
-
* @param candidateIds - Optional: only score these document IDs (for agent-scoped search)
|
|
14240
|
-
* @returns Array of { id, score } sorted by descending score
|
|
14241
|
-
*/
|
|
14242
|
-
search(query, candidateIds) {
|
|
14243
|
-
const queryStems = tokenize(query);
|
|
14244
|
-
if (queryStems.length === 0) return [];
|
|
14245
|
-
this.refreshIdf();
|
|
14246
|
-
const expandedTerms = this.expandQueryTerms(queryStems);
|
|
14247
|
-
if (expandedTerms.size === 0) return [];
|
|
14248
|
-
const avgDl = this.avgDocLen;
|
|
14249
|
-
const candidates = /* @__PURE__ */ new Set();
|
|
14250
|
-
for (const term of expandedTerms.keys()) {
|
|
14251
|
-
const posting = this.postings.get(term);
|
|
14252
|
-
if (posting) {
|
|
14253
|
-
for (const docId of posting) {
|
|
14254
|
-
if (!candidateIds || candidateIds.has(docId)) candidates.add(docId);
|
|
14255
|
-
}
|
|
14256
|
-
}
|
|
14257
|
-
}
|
|
14258
|
-
const results = [];
|
|
14259
|
-
for (const docId of candidates) {
|
|
14260
|
-
const doc = this.docs.get(docId);
|
|
14261
|
-
if (!doc) continue;
|
|
14262
|
-
let score = 0;
|
|
14263
|
-
for (const [term, weight] of expandedTerms) {
|
|
14264
|
-
const tf = doc.weightedTf.get(term) || 0;
|
|
14265
|
-
if (tf === 0) continue;
|
|
14266
|
-
const termIdf = this.idf.get(term) || 0;
|
|
14267
|
-
const numerator = tf * (BM25_K1 + 1);
|
|
14268
|
-
const denominator = tf + BM25_K1 * (1 - BM25_B + BM25_B * (doc.weightedLen / avgDl));
|
|
14269
|
-
score += termIdf * (numerator / denominator) * weight;
|
|
14270
|
-
}
|
|
14271
|
-
score += this.bigramProximityBoost(docId, queryStems);
|
|
14272
|
-
if (score > 0) results.push({ id: docId, score });
|
|
14273
|
-
}
|
|
14274
|
-
results.sort((a, b) => b.score - a.score);
|
|
14275
|
-
return results;
|
|
14276
|
-
}
|
|
14277
|
-
/** Check if a document exists in the index. */
|
|
14278
|
-
has(id) {
|
|
14279
|
-
return this.docs.has(id);
|
|
14280
|
-
}
|
|
14281
|
-
};
|
|
14282
|
-
|
|
14283
|
-
// src/memory/manager.ts
|
|
14777
|
+
init_text_search();
|
|
14284
14778
|
function sj(v, fb = {}) {
|
|
14285
14779
|
if (!v) return fb;
|
|
14286
14780
|
try {
|
|
@@ -14763,6 +15257,12 @@ var AgentMemoryManager = class {
|
|
|
14763
15257
|
};
|
|
14764
15258
|
}
|
|
14765
15259
|
};
|
|
15260
|
+
|
|
15261
|
+
// src/memory/index.ts
|
|
15262
|
+
init_text_search();
|
|
15263
|
+
|
|
15264
|
+
// src/index.ts
|
|
15265
|
+
init_skills();
|
|
14766
15266
|
// Annotate the CommonJS export names for ESM import in node:
|
|
14767
15267
|
0 && (module.exports = {
|
|
14768
15268
|
AGENT_ROLES,
|
|
@@ -14793,6 +15293,7 @@ var AgentMemoryManager = class {
|
|
|
14793
15293
|
GET_DATETIME_TOOL,
|
|
14794
15294
|
GatewayManager,
|
|
14795
15295
|
InboxWatcher,
|
|
15296
|
+
LOAD_SKILL_TOOL,
|
|
14796
15297
|
MEMORY_CATEGORIES,
|
|
14797
15298
|
MailReceiver,
|
|
14798
15299
|
MailSender,
|
|
@@ -14829,6 +15330,7 @@ var AgentMemoryManager = class {
|
|
|
14829
15330
|
RelayBridge,
|
|
14830
15331
|
RelayGateway,
|
|
14831
15332
|
SEARCH_EMAIL_TOOL,
|
|
15333
|
+
SEARCH_SKILLS_TOOL,
|
|
14832
15334
|
SPAM_THRESHOLD,
|
|
14833
15335
|
ServiceManager,
|
|
14834
15336
|
SetupManager,
|
|
@@ -14908,6 +15410,7 @@ var AgentMemoryManager = class {
|
|
|
14908
15410
|
getTelegramWebhookInfo,
|
|
14909
15411
|
hostSessionStoragePath,
|
|
14910
15412
|
inferPhoneRegion,
|
|
15413
|
+
invalidateSkillCache,
|
|
14911
15414
|
isInternalEmail,
|
|
14912
15415
|
isLoopbackMailHost,
|
|
14913
15416
|
isOperatorReplySender,
|
|
@@ -14916,7 +15419,9 @@ var AgentMemoryManager = class {
|
|
|
14916
15419
|
isTelegramChatAllowed,
|
|
14917
15420
|
isTelegramStopCommand,
|
|
14918
15421
|
isValidPhoneNumber,
|
|
15422
|
+
listSkills,
|
|
14919
15423
|
loadHostSession,
|
|
15424
|
+
loadSkill,
|
|
14920
15425
|
mapProviderSmsStatus,
|
|
14921
15426
|
nextTelegramOffset,
|
|
14922
15427
|
normalizeAddress,
|
|
@@ -14941,6 +15446,7 @@ var AgentMemoryManager = class {
|
|
|
14941
15446
|
redactSecret,
|
|
14942
15447
|
redactSmsConfig,
|
|
14943
15448
|
redactTelegramConfig,
|
|
15449
|
+
renderSkillAsPrompt,
|
|
14944
15450
|
requireBinary,
|
|
14945
15451
|
requireWhisperModel,
|
|
14946
15452
|
resolveConfig,
|
|
@@ -14949,8 +15455,10 @@ var AgentMemoryManager = class {
|
|
|
14949
15455
|
sanitizeEmail,
|
|
14950
15456
|
saveConfig,
|
|
14951
15457
|
saveHostSession,
|
|
15458
|
+
saveUserSkill,
|
|
14952
15459
|
scanOutboundEmail,
|
|
14953
15460
|
scoreEmail,
|
|
15461
|
+
searchSkills,
|
|
14954
15462
|
sendTelegramMessage,
|
|
14955
15463
|
setOperatorEmail,
|
|
14956
15464
|
setTelegramWebhook,
|
|
@@ -14963,10 +15471,12 @@ var AgentMemoryManager = class {
|
|
|
14963
15471
|
threadIdFor,
|
|
14964
15472
|
tokenize,
|
|
14965
15473
|
tryJoin,
|
|
15474
|
+
userSkillsDir,
|
|
14966
15475
|
validateApiUrl,
|
|
14967
15476
|
validatePhoneMissionPolicy,
|
|
14968
15477
|
validatePhoneMissionStart,
|
|
14969
15478
|
validatePhoneTransportProfile,
|
|
15479
|
+
validateSkill,
|
|
14970
15480
|
validateTwilioSignature,
|
|
14971
15481
|
webSearch
|
|
14972
15482
|
});
|