@danielsimonjr/memoryjs 1.9.0 → 1.9.1

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/index.cjs CHANGED
@@ -23214,7 +23214,7 @@ var ContextProfileManager = class {
23214
23214
  };
23215
23215
 
23216
23216
  // src/agent/ContextWindowManager.ts
23217
- var ContextWindowManager = class {
23217
+ var ContextWindowManager = class _ContextWindowManager {
23218
23218
  storage;
23219
23219
  salienceEngine;
23220
23220
  config;
@@ -23981,9 +23981,249 @@ var ContextWindowManager = class {
23981
23981
  console.error("[ContextWindowManager.wakeUp] L1 entity loading failed:", err);
23982
23982
  }
23983
23983
  }
23984
+ if (options.compress && l1) {
23985
+ try {
23986
+ const level = typeof options.compress === "string" ? options.compress : "medium";
23987
+ const compressResult = this.compressForContext(l1, { level });
23988
+ if (compressResult.stats.savedTokens > 0) {
23989
+ l1 = compressResult.compressed;
23990
+ }
23991
+ } catch (err) {
23992
+ console.error("[ContextWindowManager.wakeUp] Compression failed, using uncompressed:", err);
23993
+ }
23994
+ }
23984
23995
  const totalTokens = this.estimateStringTokens(l0) + this.estimateStringTokens(l1);
23985
23996
  return { l0, l1, totalTokens, entityCount };
23986
23997
  }
23998
+ // ==================== Context Compression ====================
23999
+ /** Unicode abbreviation map for aggressive-level compression (code keywords). */
24000
+ static COMMON_PATTERNS = {
24001
+ "function ": "\u0192 ",
24002
+ "return ": "\u0280 ",
24003
+ "const ": "\u1D04 ",
24004
+ "export ": "\u1D07 ",
24005
+ "import ": "\u026A ",
24006
+ "interface ": "\u026A\u0274\u1D1B ",
24007
+ "class ": "\u1D04\u029Fs ",
24008
+ "async ": "\u1D00 ",
24009
+ "await ": "\u1D00\u1D21 ",
24010
+ "undefined": "\u1D1C\u0274\u1D05",
24011
+ "null": "\u0274\u1D1C\u029F",
24012
+ "true": "\u1D1B",
24013
+ "false": "\uA730",
24014
+ "```typescript": "```ts",
24015
+ "```javascript": "```js",
24016
+ "## ": "\u2E2B ",
24017
+ "### ": "\u2E2C ",
24018
+ "#### ": "\u2E2D ",
24019
+ '"description"': '"desc"',
24020
+ '"dependencies"': '"deps"',
24021
+ '"devDependencies"': '"devDeps"',
24022
+ '"repository"': '"repo"',
24023
+ '"homepage"': '"home"',
24024
+ '"keywords"': '"keys"',
24025
+ '"license"': '"lic"',
24026
+ '"version"': '"ver"',
24027
+ '"required"': '"req"',
24028
+ '"optional"': '"opt"',
24029
+ '"default"': '"def"',
24030
+ '"example"': '"ex"',
24031
+ '"properties"': '"props"',
24032
+ '"additionalProperties"': '"addProps"',
24033
+ "node_modules/": "nm/",
24034
+ "src/": "s/",
24035
+ "dist/": "d/",
24036
+ "test/": "t/",
24037
+ "tests/": "t/",
24038
+ ".typescript": ".ts",
24039
+ ".javascript": ".js"
24040
+ };
24041
+ /**
24042
+ * Compress text for token-efficient context loading.
24043
+ * Finds repeated substrings, replaces with paragraph-sign codes, generates a legend.
24044
+ * Inspired by the CTON compress-for-context tool.
24045
+ */
24046
+ compressForContext(text, options) {
24047
+ const level = options?.level ?? "medium";
24048
+ let compressed = text;
24049
+ const legend = {};
24050
+ if (level !== "light") {
24051
+ compressed = compressed.replace(/[ \t]+/g, " ");
24052
+ compressed = compressed.replace(/\n{3,}/g, "\n\n");
24053
+ }
24054
+ if (level === "aggressive") {
24055
+ const patternResult = this.applyCommonPatterns(compressed);
24056
+ compressed = patternResult.text;
24057
+ Object.assign(legend, patternResult.legend);
24058
+ }
24059
+ const minLength2 = level === "light" ? 8 : level === "medium" ? 6 : 5;
24060
+ const minOccurrences = level === "light" ? 4 : 3;
24061
+ const maxSubstrings = level === "light" ? 20 : level === "medium" ? 30 : 36;
24062
+ const substrings = this.findRepeatedSubstrings(compressed, minLength2, minOccurrences, maxSubstrings);
24063
+ if (substrings.length > 0) {
24064
+ const totalSavings = substrings.reduce((sum, s) => sum + s.savings, 0);
24065
+ if (totalSavings > 5) {
24066
+ const result = this.applySubstringCompression(compressed, substrings);
24067
+ Object.assign(legend, result.legend);
24068
+ compressed = result.compressed;
24069
+ }
24070
+ }
24071
+ if (Object.keys(legend).length > 0) {
24072
+ const legendStr = "=== Legend ===\n" + Object.entries(legend).map(([a, f]) => `${a} = ${f}`).join("\n") + "\n=============\n\n";
24073
+ compressed = legendStr + compressed;
24074
+ }
24075
+ const originalTokens = this.estimateStringTokens(text);
24076
+ const hasLegend = Object.keys(legend).length > 0;
24077
+ const compressedTokens = hasLegend ? this.estimateStringTokens(compressed) : originalTokens;
24078
+ const savedTokens = Math.max(0, originalTokens - compressedTokens);
24079
+ return {
24080
+ compressed: hasLegend ? compressed : text,
24081
+ legend,
24082
+ stats: {
24083
+ originalTokens,
24084
+ compressedTokens,
24085
+ savedTokens,
24086
+ savedPercent: originalTokens > 0 ? Math.round(savedTokens / originalTokens * 100) : 0
24087
+ }
24088
+ };
24089
+ }
24090
+ /**
24091
+ * Compress entities for context loading. Formats entities as compact text,
24092
+ * then applies compression. Sorted by importance descending.
24093
+ */
24094
+ compressEntitiesForContext(entities, options) {
24095
+ const sorted = [...entities].filter((e) => e.entityType !== "profile" && e.entityType !== "diary").sort((a, b) => (b.importance ?? 0) - (a.importance ?? 0));
24096
+ const lines = [];
24097
+ let tokenCount = 0;
24098
+ let entityCount = 0;
24099
+ const maxTokens = options?.maxTokens ?? Infinity;
24100
+ for (const e of sorted) {
24101
+ const obs = e.observations?.slice(0, 3).join("; ") ?? "";
24102
+ const line = `[${e.entityType}] ${e.name}: ${obs}`;
24103
+ const lineTokens = this.estimateStringTokens(line);
24104
+ if (tokenCount + lineTokens > maxTokens) break;
24105
+ lines.push(line);
24106
+ tokenCount += lineTokens;
24107
+ entityCount++;
24108
+ }
24109
+ const raw = lines.join("\n");
24110
+ const result = this.compressForContext(raw, { level: options?.level });
24111
+ return { ...result, entityCount };
24112
+ }
24113
+ // ==================== Compression Helpers ====================
24114
+ /**
24115
+ * Find repeated substrings and calculate compression potential.
24116
+ * Returns substrings sorted by net savings (highest first).
24117
+ * @internal
24118
+ */
24119
+ findRepeatedSubstrings(text, minLength2, minOccurrences, maxSubstrings = 36) {
24120
+ if (text.length < minLength2 * 2) return [];
24121
+ const substringCounts = /* @__PURE__ */ new Map();
24122
+ const MAX_MAP_SIZE = 1e4;
24123
+ const tokens = text.split(/(\s+|[{}()\[\]<>:;,."'`|=])/);
24124
+ outer: for (let n = 1; n <= 6; n++) {
24125
+ for (let i = 0; i <= tokens.length - n; i++) {
24126
+ const ngram = tokens.slice(i, i + n).join("");
24127
+ if (ngram.length < minLength2 || ngram.length > 50) continue;
24128
+ if (/^\s*$/.test(ngram)) continue;
24129
+ if ((ngram.match(/\s/g) || []).length > ngram.length * 0.5) continue;
24130
+ const opens = (ngram.match(/[{(\[<]/g) || []).length;
24131
+ const closes = (ngram.match(/[})\]>]/g) || []).length;
24132
+ if (opens !== closes) continue;
24133
+ substringCounts.set(ngram, (substringCounts.get(ngram) || 0) + 1);
24134
+ if (substringCounts.size >= MAX_MAP_SIZE) break outer;
24135
+ }
24136
+ }
24137
+ const pathRe = /[a-zA-Z0-9_\-./]+\/[a-zA-Z0-9_\-./]+/g;
24138
+ let pathMatch;
24139
+ while ((pathMatch = pathRe.exec(text)) !== null) {
24140
+ const p = pathMatch[0];
24141
+ if (p.length >= minLength2 && substringCounts.size < MAX_MAP_SIZE) {
24142
+ substringCounts.set(p, (substringCounts.get(p) || 0) + 1);
24143
+ }
24144
+ }
24145
+ const candidates = [];
24146
+ for (const [substring, _mapCount] of substringCounts.entries()) {
24147
+ const actualCount = text.split(substring).length - 1;
24148
+ if (actualCount >= minOccurrences) {
24149
+ const abbrevLength = 2;
24150
+ const legendCost = abbrevLength + substring.length + 4;
24151
+ const savingsPerOccurrence = substring.length - abbrevLength;
24152
+ const netSavings = savingsPerOccurrence * actualCount - legendCost;
24153
+ if (netSavings > 5) {
24154
+ candidates.push({ substring, count: actualCount, savings: netSavings });
24155
+ }
24156
+ }
24157
+ }
24158
+ candidates.sort((a, b) => b.savings - a.savings);
24159
+ const selected = [];
24160
+ const usedSubstrings = [];
24161
+ for (const candidate of candidates) {
24162
+ let isTooSimilar = false;
24163
+ const candidateTrimmed = candidate.substring.trim();
24164
+ if (candidateTrimmed.length < 3) continue;
24165
+ for (const used of usedSubstrings) {
24166
+ const usedTrimmed = used.trim();
24167
+ if (used.includes(candidate.substring) || candidate.substring.includes(used)) {
24168
+ isTooSimilar = true;
24169
+ break;
24170
+ }
24171
+ if (candidateTrimmed === usedTrimmed || candidateTrimmed.includes(usedTrimmed) || usedTrimmed.includes(candidateTrimmed)) {
24172
+ isTooSimilar = true;
24173
+ break;
24174
+ }
24175
+ const shorter = candidateTrimmed.length < usedTrimmed.length ? candidateTrimmed : usedTrimmed;
24176
+ const longer = candidateTrimmed.length >= usedTrimmed.length ? candidateTrimmed : usedTrimmed;
24177
+ if (longer.includes(shorter.slice(0, Math.floor(shorter.length * 0.7)))) {
24178
+ isTooSimilar = true;
24179
+ break;
24180
+ }
24181
+ }
24182
+ if (!isTooSimilar) {
24183
+ selected.push(candidate);
24184
+ usedSubstrings.push(candidate.substring);
24185
+ if (selected.length >= maxSubstrings) break;
24186
+ }
24187
+ }
24188
+ return selected;
24189
+ }
24190
+ /**
24191
+ * Apply substring replacements to text.
24192
+ * Applies replacements in savings-descending order (as returned by findRepeatedSubstrings).
24193
+ * @internal
24194
+ */
24195
+ applySubstringCompression(text, substrings) {
24196
+ const legend = {};
24197
+ let compressed = text;
24198
+ substrings.forEach((item, index) => {
24199
+ const abbrev = `\xA7${index.toString(36)}`;
24200
+ legend[abbrev] = item.substring;
24201
+ compressed = compressed.split(item.substring).join(abbrev);
24202
+ });
24203
+ return { compressed, legend };
24204
+ }
24205
+ /**
24206
+ * Apply COMMON_PATTERNS unicode abbreviations (aggressive level only).
24207
+ * @internal
24208
+ */
24209
+ applyCommonPatterns(text) {
24210
+ let result = text;
24211
+ const legend = {};
24212
+ for (const [pattern2, replacement] of Object.entries(_ContextWindowManager.COMMON_PATTERNS)) {
24213
+ try {
24214
+ if (!result.includes(pattern2)) continue;
24215
+ const count = result.split(pattern2).length - 1;
24216
+ const savings = (pattern2.length - replacement.length) * count;
24217
+ if (savings > pattern2.length + replacement.length + 5) {
24218
+ legend[replacement] = pattern2;
24219
+ result = result.split(pattern2).join(replacement);
24220
+ }
24221
+ } catch {
24222
+ continue;
24223
+ }
24224
+ }
24225
+ return { text: result, legend };
24226
+ }
23987
24227
  };
23988
24228
 
23989
24229
  // src/agent/MemoryFormatter.ts