@agentmemory/agentmemory 0.8.7 → 0.8.9

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-CqxObfG_.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-CfbSegvn.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([
@@ -3898,7 +4015,9 @@ function registerExportImportFunction(sdk, kv) {
3898
4015
  "0.8.4",
3899
4016
  "0.8.5",
3900
4017
  "0.8.6",
3901
- "0.8.7"
4018
+ "0.8.7",
4019
+ "0.8.8",
4020
+ "0.8.9"
3902
4021
  ]).has(importData.version)) return {
3903
4022
  success: false,
3904
4023
  error: `Unsupported export version: ${importData.version}`
@@ -13815,6 +13934,8 @@ async function main() {
13815
13934
  }
13816
13935
  registerConsolidationPipelineFunction(sdk, kv, provider);
13817
13936
  console.log(`[agentmemory] Consolidation pipeline: registered (CONSOLIDATION_ENABLED=${isConsolidationEnabled() ? "true" : "false"})`);
13937
+ 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.`);
13938
+ 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).`);
13818
13939
  const teamConfig = loadTeamConfig();
13819
13940
  if (teamConfig) {
13820
13941
  registerTeamFunction(sdk, kv, teamConfig);
@@ -13940,4 +14061,4 @@ main().catch((err) => {
13940
14061
 
13941
14062
  //#endregion
13942
14063
  export { };
13943
- //# sourceMappingURL=src-DUTxjrOM.mjs.map
14064
+ //# sourceMappingURL=src-DJpwR1mt.mjs.map