@agentmemory/agentmemory 0.8.6 → 0.8.8

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.
@@ -1,5 +1,5 @@
1
1
  import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./cli.mjs";
2
- import { a as getEnvVar, c as isGraphExtractionEnabled, d as loadEmbeddingConfig, f as loadFallbackConfig, i as getConsolidationDecayDays, l as loadClaudeBridgeConfig, m as loadTeamConfig, n as VERSION, p as loadSnapshotConfig, r as detectEmbeddingProvider, s as isConsolidationEnabled, t as getVisibleTools, u as loadConfig } from "./tools-registry-BYzapMJi.mjs";
2
+ import { a as getEnvVar, c as isConsolidationEnabled, d as loadConfig, f as loadEmbeddingConfig, h as loadTeamConfig, i as getConsolidationDecayDays, l as isGraphExtractionEnabled, m as loadSnapshotConfig, n as VERSION, p as loadFallbackConfig, r as detectEmbeddingProvider, s as isAutoCompressEnabled, t as getVisibleTools, u as loadClaudeBridgeConfig } from "./tools-registry-BuDo4gKj.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join, resolve, sep } from "node:path";
@@ -1777,164 +1777,95 @@ function withKeyedLock(key, fn) {
1777
1777
  }
1778
1778
 
1779
1779
  //#endregion
1780
- //#region src/functions/observe.ts
1781
- function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
1782
- sdk.registerFunction({
1783
- id: "mem::observe",
1784
- description: "Capture and store a tool-use observation"
1785
- }, async (payload) => {
1786
- const ctx = getContext();
1787
- if (!payload?.sessionId || typeof payload.sessionId !== "string" || !payload.hookType || typeof payload.hookType !== "string" || !payload.timestamp || typeof payload.timestamp !== "string") return {
1788
- success: false,
1789
- error: "Invalid payload: sessionId, hookType, and timestamp are required"
1790
- };
1791
- const obsId = generateId("obs");
1792
- let dedupHash;
1793
- if (dedupMap) {
1794
- const d = typeof payload.data === "object" && payload.data !== null ? payload.data : {};
1795
- const toolName = d["tool_name"] || payload.hookType;
1796
- dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);
1797
- if (dedupMap.isDuplicate(dedupHash)) return {
1798
- deduplicated: true,
1799
- sessionId: payload.sessionId
1800
- };
1801
- }
1802
- let sanitizedRaw = payload.data;
1803
- try {
1804
- const sanitized = stripPrivateData(JSON.stringify(payload.data));
1805
- sanitizedRaw = JSON.parse(sanitized);
1806
- } catch {
1807
- sanitizedRaw = stripPrivateData(String(payload.data));
1808
- }
1809
- const raw = {
1810
- id: obsId,
1811
- sessionId: payload.sessionId,
1812
- timestamp: payload.timestamp,
1813
- hookType: payload.hookType,
1814
- raw: sanitizedRaw
1815
- };
1816
- if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) {
1817
- const d = sanitizedRaw;
1818
- if (payload.hookType === "post_tool_use" || payload.hookType === "post_tool_failure") {
1819
- raw.toolName = d["tool_name"];
1820
- raw.toolInput = d["tool_input"];
1821
- raw.toolOutput = d["tool_output"] || d["error"];
1822
- }
1823
- if (payload.hookType === "prompt_submit") raw.userPrompt = d["prompt"];
1824
- }
1825
- return withKeyedLock(`obs:${payload.sessionId}`, async () => {
1826
- if (maxObservationsPerSession && maxObservationsPerSession > 0) {
1827
- if ((await kv.list(KV.observations(payload.sessionId))).length >= maxObservationsPerSession) return {
1828
- success: false,
1829
- error: `Session observation limit reached (${maxObservationsPerSession})`
1830
- };
1831
- }
1832
- await kv.set(KV.observations(payload.sessionId), obsId, raw);
1833
- if (dedupMap && dedupHash) dedupMap.record(dedupHash);
1834
- sdk.triggerVoid("stream::set", {
1835
- stream_name: STREAM.name,
1836
- group_id: STREAM.group(payload.sessionId),
1837
- item_id: obsId,
1838
- data: {
1839
- type: "raw",
1840
- observation: raw
1841
- }
1842
- });
1843
- sdk.triggerVoid("stream::set", {
1844
- stream_name: STREAM.name,
1845
- group_id: STREAM.viewerGroup,
1846
- item_id: obsId,
1847
- data: {
1848
- type: "raw",
1849
- observation: raw,
1850
- sessionId: payload.sessionId
1851
- }
1852
- });
1853
- const session = await kv.get(KV.sessions, payload.sessionId);
1854
- if (session) await kv.set(KV.sessions, payload.sessionId, {
1855
- ...session,
1856
- observationCount: (session.observationCount || 0) + 1
1857
- });
1858
- sdk.triggerVoid("mem::compress", {
1859
- observationId: obsId,
1860
- sessionId: payload.sessionId,
1861
- raw
1862
- });
1863
- ctx.logger.info("Observation captured", {
1864
- obsId,
1865
- sessionId: payload.sessionId,
1866
- hook: payload.hookType
1867
- });
1868
- return { observationId: obsId };
1869
- });
1870
- });
1871
- }
1872
-
1873
- //#endregion
1874
- //#region src/prompts/compression.ts
1875
- const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
1876
-
1877
- Output EXACTLY this XML format with no additional text:
1878
-
1879
- <observation>
1880
- <type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type>
1881
- <title>Short descriptive title (max 80 chars)</title>
1882
- <subtitle>One-line context (optional)</subtitle>
1883
- <facts>
1884
- <fact>Specific factual detail 1</fact>
1885
- <fact>Specific factual detail 2</fact>
1886
- </facts>
1887
- <narrative>2-3 sentence summary of what happened and why it matters</narrative>
1888
- <concepts>
1889
- <concept>technical concept or pattern</concept>
1890
- </concepts>
1891
- <files>
1892
- <file>path/to/file</file>
1893
- </files>
1894
- <importance>1-10 scale, 10 being critical architectural decision</importance>
1895
- </observation>
1896
-
1897
- Rules:
1898
- - Be concise but preserve ALL technically relevant details
1899
- - File paths must be exact
1900
- - Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
1901
- - Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
1902
- - Strip any secrets, tokens, or credentials from the output`;
1903
- function buildCompressionPrompt(observation) {
1904
- const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
1905
- if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
1906
- if (observation.toolInput) {
1907
- const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
1908
- parts.push(`Input:\n${truncate(input, 4e3)}`);
1909
- }
1910
- if (observation.toolOutput) {
1911
- const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
1912
- parts.push(`Output:\n${truncate(output, 4e3)}`);
1780
+ //#region src/functions/compress-synthetic.ts
1781
+ function inferType(toolName, hookType) {
1782
+ if (hookType === "post_tool_failure") return "error";
1783
+ if (hookType === "prompt_submit") return "conversation";
1784
+ if (hookType === "subagent_stop" || hookType === "task_completed") return "subagent";
1785
+ if (hookType === "notification") return "notification";
1786
+ if (!toolName) return "other";
1787
+ const n = toolName.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
1788
+ const hasWord = (word) => new RegExp(`(^|_)${word}(_|$)`).test(n) || n === word || n.endsWith(word) || n.startsWith(word);
1789
+ if ([
1790
+ "fetch",
1791
+ "http",
1792
+ "web"
1793
+ ].some(hasWord)) return "web_fetch";
1794
+ if ([
1795
+ "grep",
1796
+ "search",
1797
+ "glob",
1798
+ "find"
1799
+ ].some(hasWord)) return "search";
1800
+ if ([
1801
+ "bash",
1802
+ "shell",
1803
+ "exec",
1804
+ "run"
1805
+ ].some(hasWord)) return "command_run";
1806
+ if ([
1807
+ "edit",
1808
+ "update",
1809
+ "patch",
1810
+ "replace"
1811
+ ].some(hasWord)) return "file_edit";
1812
+ if (["write", "create"].some(hasWord)) return "file_write";
1813
+ if (["read", "view"].some(hasWord)) return "file_read";
1814
+ if (["task", "agent"].some(hasWord)) return "subagent";
1815
+ return "other";
1816
+ }
1817
+ function extractFiles$1(input) {
1818
+ if (!input || typeof input !== "object") return [];
1819
+ const o = input;
1820
+ const out = /* @__PURE__ */ new Set();
1821
+ for (const key of [
1822
+ "file_path",
1823
+ "filepath",
1824
+ "path",
1825
+ "filePath",
1826
+ "file",
1827
+ "pattern"
1828
+ ]) {
1829
+ const v = o[key];
1830
+ if (typeof v === "string" && v.length > 0 && v.length < 512) out.add(v);
1831
+ }
1832
+ return [...out];
1833
+ }
1834
+ function stringifyForNarrative(v) {
1835
+ if (v == null) return "";
1836
+ if (typeof v === "string") return v;
1837
+ try {
1838
+ return JSON.stringify(v);
1839
+ } catch {
1840
+ return String(v);
1913
1841
  }
1914
- if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
1915
- return parts.join("\n\n");
1916
- }
1917
- function truncate(s, max) {
1918
- return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
1919
1842
  }
1920
-
1921
- //#endregion
1922
- //#region src/prompts/xml.ts
1923
- const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
1924
- function getXmlTag(xml, tag) {
1925
- if (!VALID_TAG.test(tag)) return "";
1926
- const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
1927
- return match ? match[1].trim() : "";
1843
+ function truncate$1(s, n) {
1844
+ return s.length > n ? s.slice(0, n - 1) + "…" : s;
1928
1845
  }
1929
- function getXmlChildren(xml, parentTag, childTag) {
1930
- if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return [];
1931
- const parentMatch = xml.match(new RegExp(`<${parentTag}>([\\s\\S]*?)</${parentTag}>`));
1932
- if (!parentMatch) return [];
1933
- const items = [];
1934
- const re = new RegExp(`<${childTag}>([\\s\\S]*?)</${childTag}>`, "g");
1935
- let m;
1936
- while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
1937
- return items;
1846
+ function buildSyntheticCompression(raw) {
1847
+ const toolName = raw.toolName ?? raw.hookType;
1848
+ const inputStr = stringifyForNarrative(raw.toolInput);
1849
+ const outputStr = stringifyForNarrative(raw.toolOutput);
1850
+ const narrativeParts = [
1851
+ raw.userPrompt ?? "",
1852
+ inputStr,
1853
+ outputStr
1854
+ ].filter((s) => s.length > 0);
1855
+ return {
1856
+ id: raw.id,
1857
+ sessionId: raw.sessionId,
1858
+ timestamp: raw.timestamp,
1859
+ type: inferType(toolName, raw.hookType),
1860
+ title: truncate$1(toolName || "observation", 80),
1861
+ subtitle: inputStr ? truncate$1(inputStr, 120) : void 0,
1862
+ facts: [],
1863
+ narrative: truncate$1(narrativeParts.join(" | "), 400),
1864
+ concepts: [],
1865
+ files: extractFiles$1(raw.toolInput),
1866
+ importance: 5,
1867
+ confidence: .3
1868
+ };
1938
1869
  }
1939
1870
 
1940
1871
  //#endregion
@@ -2105,6 +2036,192 @@ function registerSearchFunction(sdk, kv) {
2105
2036
  });
2106
2037
  }
2107
2038
 
2039
+ //#endregion
2040
+ //#region src/functions/observe.ts
2041
+ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
2042
+ sdk.registerFunction({
2043
+ id: "mem::observe",
2044
+ description: "Capture and store a tool-use observation"
2045
+ }, async (payload) => {
2046
+ const ctx = getContext();
2047
+ if (!payload?.sessionId || typeof payload.sessionId !== "string" || !payload.hookType || typeof payload.hookType !== "string" || !payload.timestamp || typeof payload.timestamp !== "string") return {
2048
+ success: false,
2049
+ error: "Invalid payload: sessionId, hookType, and timestamp are required"
2050
+ };
2051
+ const obsId = generateId("obs");
2052
+ let dedupHash;
2053
+ if (dedupMap) {
2054
+ const d = typeof payload.data === "object" && payload.data !== null ? payload.data : {};
2055
+ const toolName = d["tool_name"] || payload.hookType;
2056
+ dedupHash = dedupMap.computeHash(payload.sessionId, toolName, d["tool_input"]);
2057
+ if (dedupMap.isDuplicate(dedupHash)) return {
2058
+ deduplicated: true,
2059
+ sessionId: payload.sessionId
2060
+ };
2061
+ }
2062
+ let sanitizedRaw = payload.data;
2063
+ try {
2064
+ const sanitized = stripPrivateData(JSON.stringify(payload.data));
2065
+ sanitizedRaw = JSON.parse(sanitized);
2066
+ } catch {
2067
+ sanitizedRaw = stripPrivateData(String(payload.data));
2068
+ }
2069
+ const raw = {
2070
+ id: obsId,
2071
+ sessionId: payload.sessionId,
2072
+ timestamp: payload.timestamp,
2073
+ hookType: payload.hookType,
2074
+ raw: sanitizedRaw
2075
+ };
2076
+ if (typeof sanitizedRaw === "object" && sanitizedRaw !== null) {
2077
+ const d = sanitizedRaw;
2078
+ if (payload.hookType === "post_tool_use" || payload.hookType === "post_tool_failure") {
2079
+ raw.toolName = d["tool_name"];
2080
+ raw.toolInput = d["tool_input"];
2081
+ raw.toolOutput = d["tool_output"] || d["error"];
2082
+ }
2083
+ if (payload.hookType === "prompt_submit") raw.userPrompt = d["prompt"];
2084
+ }
2085
+ return withKeyedLock(`obs:${payload.sessionId}`, async () => {
2086
+ if (maxObservationsPerSession && maxObservationsPerSession > 0) {
2087
+ if ((await kv.list(KV.observations(payload.sessionId))).length >= maxObservationsPerSession) return {
2088
+ success: false,
2089
+ error: `Session observation limit reached (${maxObservationsPerSession})`
2090
+ };
2091
+ }
2092
+ await kv.set(KV.observations(payload.sessionId), obsId, raw);
2093
+ if (dedupMap && dedupHash) dedupMap.record(dedupHash);
2094
+ sdk.triggerVoid("stream::set", {
2095
+ stream_name: STREAM.name,
2096
+ group_id: STREAM.group(payload.sessionId),
2097
+ item_id: obsId,
2098
+ data: {
2099
+ type: "raw",
2100
+ observation: raw
2101
+ }
2102
+ });
2103
+ sdk.triggerVoid("stream::set", {
2104
+ stream_name: STREAM.name,
2105
+ group_id: STREAM.viewerGroup,
2106
+ item_id: obsId,
2107
+ data: {
2108
+ type: "raw",
2109
+ observation: raw,
2110
+ sessionId: payload.sessionId
2111
+ }
2112
+ });
2113
+ const session = await kv.get(KV.sessions, payload.sessionId);
2114
+ if (session) await kv.set(KV.sessions, payload.sessionId, {
2115
+ ...session,
2116
+ observationCount: (session.observationCount || 0) + 1
2117
+ });
2118
+ if (isAutoCompressEnabled()) sdk.triggerVoid("mem::compress", {
2119
+ observationId: obsId,
2120
+ sessionId: payload.sessionId,
2121
+ raw
2122
+ });
2123
+ else {
2124
+ const synthetic = buildSyntheticCompression(raw);
2125
+ await kv.set(KV.observations(payload.sessionId), obsId, synthetic);
2126
+ getSearchIndex().add(synthetic);
2127
+ sdk.triggerVoid("stream::set", {
2128
+ stream_name: STREAM.name,
2129
+ group_id: STREAM.group(payload.sessionId),
2130
+ item_id: obsId,
2131
+ data: {
2132
+ type: "compressed",
2133
+ observation: synthetic
2134
+ }
2135
+ });
2136
+ sdk.triggerVoid("stream::set", {
2137
+ stream_name: STREAM.name,
2138
+ group_id: STREAM.viewerGroup,
2139
+ item_id: obsId,
2140
+ data: {
2141
+ type: "compressed",
2142
+ observation: synthetic,
2143
+ sessionId: payload.sessionId
2144
+ }
2145
+ });
2146
+ }
2147
+ ctx.logger.info("Observation captured", {
2148
+ obsId,
2149
+ sessionId: payload.sessionId,
2150
+ hook: payload.hookType,
2151
+ compress: isAutoCompressEnabled() ? "llm" : "synthetic"
2152
+ });
2153
+ return { observationId: obsId };
2154
+ });
2155
+ });
2156
+ }
2157
+
2158
+ //#endregion
2159
+ //#region src/prompts/compression.ts
2160
+ const COMPRESSION_SYSTEM = `You are a memory compression engine for an AI coding agent. Your job is to extract the essential information from a tool usage observation and compress it into structured data.
2161
+
2162
+ Output EXACTLY this XML format with no additional text:
2163
+
2164
+ <observation>
2165
+ <type>one of: file_read, file_write, file_edit, command_run, search, web_fetch, conversation, error, decision, discovery, subagent, notification, task, other</type>
2166
+ <title>Short descriptive title (max 80 chars)</title>
2167
+ <subtitle>One-line context (optional)</subtitle>
2168
+ <facts>
2169
+ <fact>Specific factual detail 1</fact>
2170
+ <fact>Specific factual detail 2</fact>
2171
+ </facts>
2172
+ <narrative>2-3 sentence summary of what happened and why it matters</narrative>
2173
+ <concepts>
2174
+ <concept>technical concept or pattern</concept>
2175
+ </concepts>
2176
+ <files>
2177
+ <file>path/to/file</file>
2178
+ </files>
2179
+ <importance>1-10 scale, 10 being critical architectural decision</importance>
2180
+ </observation>
2181
+
2182
+ Rules:
2183
+ - Be concise but preserve ALL technically relevant details
2184
+ - File paths must be exact
2185
+ - Importance: 1-3 for routine reads, 4-6 for edits/commands, 7-9 for architectural decisions, 10 for breaking changes
2186
+ - Concepts should be reusable search terms (e.g., "React hooks", "SQL migration", "auth middleware")
2187
+ - Strip any secrets, tokens, or credentials from the output`;
2188
+ function buildCompressionPrompt(observation) {
2189
+ const parts = [`Timestamp: ${observation.timestamp}`, `Hook: ${observation.hookType}`];
2190
+ if (observation.toolName) parts.push(`Tool: ${observation.toolName}`);
2191
+ if (observation.toolInput) {
2192
+ const input = typeof observation.toolInput === "string" ? observation.toolInput : JSON.stringify(observation.toolInput, null, 2);
2193
+ parts.push(`Input:\n${truncate(input, 4e3)}`);
2194
+ }
2195
+ if (observation.toolOutput) {
2196
+ const output = typeof observation.toolOutput === "string" ? observation.toolOutput : JSON.stringify(observation.toolOutput, null, 2);
2197
+ parts.push(`Output:\n${truncate(output, 4e3)}`);
2198
+ }
2199
+ if (observation.userPrompt) parts.push(`User prompt:\n${truncate(observation.userPrompt, 2e3)}`);
2200
+ return parts.join("\n\n");
2201
+ }
2202
+ function truncate(s, max) {
2203
+ return s.length > max ? s.slice(0, max) + "\n[...truncated]" : s;
2204
+ }
2205
+
2206
+ //#endregion
2207
+ //#region src/prompts/xml.ts
2208
+ const VALID_TAG = /^[a-zA-Z_][a-zA-Z0-9_-]*$/;
2209
+ function getXmlTag(xml, tag) {
2210
+ if (!VALID_TAG.test(tag)) return "";
2211
+ const match = xml.match(new RegExp(`<${tag}>([\\s\\S]*?)</${tag}>`));
2212
+ return match ? match[1].trim() : "";
2213
+ }
2214
+ function getXmlChildren(xml, parentTag, childTag) {
2215
+ if (!VALID_TAG.test(parentTag) || !VALID_TAG.test(childTag)) return [];
2216
+ const parentMatch = xml.match(new RegExp(`<${parentTag}>([\\s\\S]*?)</${parentTag}>`));
2217
+ if (!parentMatch) return [];
2218
+ const items = [];
2219
+ const re = new RegExp(`<${childTag}>([\\s\\S]*?)</${childTag}>`, "g");
2220
+ let m;
2221
+ while ((m = re.exec(parentMatch[1])) !== null) items.push(m[1].trim());
2222
+ return items;
2223
+ }
2224
+
2108
2225
  //#endregion
2109
2226
  //#region src/eval/schemas.ts
2110
2227
  const HookTypeEnum = z.enum([
@@ -3897,7 +4014,9 @@ function registerExportImportFunction(sdk, kv) {
3897
4014
  "0.8.3",
3898
4015
  "0.8.4",
3899
4016
  "0.8.5",
3900
- "0.8.6"
4017
+ "0.8.6",
4018
+ "0.8.7",
4019
+ "0.8.8"
3901
4020
  ]).has(importData.version)) return {
3902
4021
  success: false,
3903
4022
  error: `Unsupported export version: ${importData.version}`
@@ -13814,6 +13933,8 @@ async function main() {
13814
13933
  }
13815
13934
  registerConsolidationPipelineFunction(sdk, kv, provider);
13816
13935
  console.log(`[agentmemory] Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
13936
+ if (isAutoCompressEnabled()) console.log(`[agentmemory] WARNING: AGENTMEMORY_AUTO_COMPRESS=true — every PostToolUse observation will be sent to your LLM provider for compression. This spends API tokens proportional to your session tool-use frequency (see #138). Set AGENTMEMORY_AUTO_COMPRESS=false to disable.`);
13937
+ else console.log(`[agentmemory] Auto-compress: OFF (default, #138) — observations indexed via zero-LLM synthetic compression. Set AGENTMEMORY_AUTO_COMPRESS=true to opt-in to LLM-powered summaries (uses your API key).`);
13817
13938
  const teamConfig = loadTeamConfig();
13818
13939
  if (teamConfig) {
13819
13940
  registerTeamFunction(sdk, kv, teamConfig);
@@ -13939,4 +14060,4 @@ main().catch((err) => {
13939
14060
 
13940
14061
  //#endregion
13941
14062
  export { };
13942
- //# sourceMappingURL=src-DjVaT_sI.mjs.map
14063
+ //# sourceMappingURL=src-CneY0pgf.mjs.map