@agentmemory/agentmemory 0.8.11 → 0.8.12

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,8 +1,8 @@
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 isConsolidationEnabled, d as loadClaudeBridgeConfig, f as loadConfig, g as loadTeamConfig, h as loadSnapshotConfig, i as getConsolidationDecayDays, l as isContextInjectionEnabled, m as loadFallbackConfig, n as VERSION, p as loadEmbeddingConfig, r as detectEmbeddingProvider, s as isAutoCompressEnabled, t as getVisibleTools, u as isGraphExtractionEnabled } from "./tools-registry-DmTkd0if.mjs";
2
+ import { a as getEnvVar, c as isConsolidationEnabled, d as loadClaudeBridgeConfig, f as loadConfig, g as loadTeamConfig, h as loadSnapshotConfig, i as getConsolidationDecayDays, l as isContextInjectionEnabled, m as loadFallbackConfig, n as VERSION, p as loadEmbeddingConfig, r as detectEmbeddingProvider, s as isAutoCompressEnabled, t as getVisibleTools, u as isGraphExtractionEnabled } from "./tools-registry-D96ukJg4.mjs";
3
3
  import { execFile } from "node:child_process";
4
4
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, join, resolve, sep } from "node:path";
5
+ import { basename, dirname, extname, join, resolve, sep } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { homedir } from "node:os";
8
8
  import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
@@ -12,7 +12,7 @@ import { z } from "zod";
12
12
  import { promisify } from "node:util";
13
13
  import { lookup } from "node:dns/promises";
14
14
  import { isIP } from "node:net";
15
- import { mkdir, writeFile } from "node:fs/promises";
15
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
16
16
  import { createServer } from "node:http";
17
17
 
18
18
  //#region src/providers/agent-sdk.ts
@@ -2041,6 +2041,17 @@ function registerSearchFunction(sdk, kv) {
2041
2041
  }
2042
2042
  const projectFilter = typeof data.project === "string" && data.project.length > 0 ? data.project : void 0;
2043
2043
  const cwdFilter = typeof data.cwd === "string" && data.cwd.length > 0 ? data.cwd : void 0;
2044
+ const format = typeof data.format === "string" ? data.format : "full";
2045
+ if (![
2046
+ "full",
2047
+ "compact",
2048
+ "narrative"
2049
+ ].includes(format)) throw new Error("mem::search: format must be one of 'full', 'compact', or 'narrative'");
2050
+ let tokenBudget;
2051
+ if (data.token_budget !== void 0) {
2052
+ if (!Number.isInteger(data.token_budget) || data.token_budget < 1) throw new Error("mem::search: token_budget must be a positive integer");
2053
+ tokenBudget = data.token_budget;
2054
+ }
2044
2055
  if (idx.size === 0) {
2045
2056
  const count = await rebuildIndex(kv);
2046
2057
  logger.info("Search index rebuilt", { entries: count });
@@ -2077,13 +2088,81 @@ function registerSearchFunction(sdk, kv) {
2077
2088
  });
2078
2089
  }
2079
2090
  recordAccessBatch(kv, enriched.map((r) => r.observation.id));
2091
+ const estimateTokens = (value) => Math.max(1, Math.ceil(JSON.stringify(value).length / 3));
2092
+ const applyTokenBudget = (items) => {
2093
+ if (!tokenBudget) return {
2094
+ items,
2095
+ used: items.reduce((sum, item) => sum + estimateTokens(item), 0),
2096
+ truncated: false
2097
+ };
2098
+ const selected = [];
2099
+ let used = 0;
2100
+ for (const item of items) {
2101
+ const itemTokens = estimateTokens(item);
2102
+ if (used + itemTokens > tokenBudget) return {
2103
+ items: selected,
2104
+ used,
2105
+ truncated: selected.length < items.length
2106
+ };
2107
+ selected.push(item);
2108
+ used += itemTokens;
2109
+ }
2110
+ return {
2111
+ items: selected,
2112
+ used,
2113
+ truncated: false
2114
+ };
2115
+ };
2116
+ if (format === "compact") {
2117
+ const packed = applyTokenBudget(enriched.map((r) => ({
2118
+ obsId: r.observation.id,
2119
+ sessionId: r.sessionId,
2120
+ title: r.observation.title,
2121
+ type: r.observation.type,
2122
+ score: r.score,
2123
+ timestamp: r.observation.timestamp
2124
+ })));
2125
+ return {
2126
+ format,
2127
+ results: packed.items,
2128
+ tokens_used: packed.used,
2129
+ tokens_budget: tokenBudget,
2130
+ truncated: packed.truncated
2131
+ };
2132
+ }
2133
+ if (format === "narrative") {
2134
+ const packed = applyTokenBudget(enriched.map((r) => ({
2135
+ obsId: r.observation.id,
2136
+ sessionId: r.sessionId,
2137
+ title: r.observation.title,
2138
+ narrative: r.observation.narrative,
2139
+ score: r.score,
2140
+ timestamp: r.observation.timestamp
2141
+ })));
2142
+ const text = packed.items.map((r, index) => `${index + 1}. ${r.title}\n${r.narrative}`).join("\n\n");
2143
+ return {
2144
+ format,
2145
+ results: packed.items,
2146
+ text,
2147
+ tokens_used: packed.used,
2148
+ tokens_budget: tokenBudget,
2149
+ truncated: packed.truncated
2150
+ };
2151
+ }
2152
+ const packed = applyTokenBudget(enriched);
2080
2153
  logger.info("Search completed", {
2081
2154
  query,
2082
- results: enriched.length,
2155
+ results: packed.items.length,
2083
2156
  hasProjectFilter: !!projectFilter,
2084
2157
  hasCwdFilter: !!cwdFilter
2085
2158
  });
2086
- return { results: enriched };
2159
+ return {
2160
+ format,
2161
+ results: packed.items,
2162
+ tokens_used: packed.used,
2163
+ tokens_budget: tokenBudget,
2164
+ truncated: packed.truncated
2165
+ };
2087
2166
  });
2088
2167
  }
2089
2168
 
@@ -4283,7 +4362,8 @@ function registerExportImportFunction(sdk, kv) {
4283
4362
  "0.8.8",
4284
4363
  "0.8.9",
4285
4364
  "0.8.10",
4286
- "0.8.11"
4365
+ "0.8.11",
4366
+ "0.8.12"
4287
4367
  ]).has(importData.version)) return {
4288
4368
  success: false,
4289
4369
  error: `Unsupported export version: ${importData.version}`
@@ -10702,6 +10782,121 @@ function registerRetentionFunctions(sdk, kv) {
10702
10782
  });
10703
10783
  }
10704
10784
 
10785
+ //#endregion
10786
+ //#region src/functions/compress-file.ts
10787
+ const SENSITIVE_PATH_TERMS = [
10788
+ "secret",
10789
+ "credential",
10790
+ "private_key",
10791
+ ".env",
10792
+ "id_rsa",
10793
+ "token"
10794
+ ];
10795
+ const COMPRESS_FILE_SYSTEM_PROMPT = `You compress markdown while preserving structure.
10796
+ Rules:
10797
+ - Keep all headings exactly as-is.
10798
+ - Keep all URLs exactly as-is.
10799
+ - Keep all fenced code blocks exactly as-is.
10800
+ - Do not remove sections; shorten prose under each section.
10801
+ - Output only markdown, no wrappers or explanations.`;
10802
+ function stripMarkdownFence(text) {
10803
+ const trimmed = text.trim();
10804
+ const match = trimmed.match(/^```(?:markdown|md)?\s*([\s\S]*?)\s*```$/i);
10805
+ return match ? match[1].trim() : trimmed;
10806
+ }
10807
+ function extractUrls(text) {
10808
+ return Array.from(new Set(text.match(/https?:\/\/[^\s)]+/g) || []));
10809
+ }
10810
+ function extractHeadings(text) {
10811
+ return text.split("\n").map((line) => line.trim()).filter((line) => /^#{1,6}\s+/.test(line));
10812
+ }
10813
+ function extractCodeBlocks(text) {
10814
+ return text.match(/```[\s\S]*?```/g) || [];
10815
+ }
10816
+ function validateCompression(original, compressed) {
10817
+ const errors = [];
10818
+ const originalHeadings = extractHeadings(original);
10819
+ const compressedHeadings = extractHeadings(compressed);
10820
+ for (const heading of originalHeadings) if (!compressedHeadings.includes(heading)) errors.push(`missing heading: ${heading}`);
10821
+ const originalUrls = extractUrls(original).sort();
10822
+ const compressedUrls = extractUrls(compressed).sort();
10823
+ if (originalUrls.length !== compressedUrls.length) errors.push("url count changed");
10824
+ else for (let i = 0; i < originalUrls.length; i++) if (originalUrls[i] !== compressedUrls[i]) {
10825
+ errors.push("url set changed");
10826
+ break;
10827
+ }
10828
+ const originalBlocks = extractCodeBlocks(original);
10829
+ const compressedBlocks = extractCodeBlocks(compressed);
10830
+ if (originalBlocks.length !== compressedBlocks.length) errors.push("code block count changed");
10831
+ else for (let i = 0; i < originalBlocks.length; i++) if (originalBlocks[i] !== compressedBlocks[i]) {
10832
+ errors.push("code block content changed");
10833
+ break;
10834
+ }
10835
+ return errors;
10836
+ }
10837
+ function resolveBackupPath(filePath) {
10838
+ const base = basename(filePath, extname(filePath));
10839
+ const name = base.endsWith(".original") ? base : `${base}.original`;
10840
+ return join(dirname(filePath), `${name}.md`);
10841
+ }
10842
+ function registerCompressFileFunction(sdk, kv, provider) {
10843
+ sdk.registerFunction("mem::compress-file", async (data) => {
10844
+ if (!data?.filePath || typeof data.filePath !== "string") return {
10845
+ success: false,
10846
+ error: "filePath is required"
10847
+ };
10848
+ const absolutePath = resolve(data.filePath);
10849
+ const lowerPath = absolutePath.toLowerCase();
10850
+ if (extname(absolutePath).toLowerCase() !== ".md") return {
10851
+ success: false,
10852
+ error: "filePath must point to a .md file"
10853
+ };
10854
+ if (SENSITIVE_PATH_TERMS.some((term) => lowerPath.includes(term))) return {
10855
+ success: false,
10856
+ error: "refusing to process sensitive-looking path"
10857
+ };
10858
+ let original;
10859
+ try {
10860
+ original = await readFile(absolutePath, "utf-8");
10861
+ } catch {
10862
+ return {
10863
+ success: false,
10864
+ error: "failed to read file"
10865
+ };
10866
+ }
10867
+ if (!original.trim()) return {
10868
+ success: true,
10869
+ skipped: true,
10870
+ reason: "file is empty"
10871
+ };
10872
+ const compressed = stripMarkdownFence(await provider.summarize(COMPRESS_FILE_SYSTEM_PROMPT, `Compress this markdown file while preserving structure and code blocks:\n\n${original}`));
10873
+ const validationErrors = validateCompression(original, compressed);
10874
+ if (validationErrors.length > 0) return {
10875
+ success: false,
10876
+ error: "compression validation failed",
10877
+ details: validationErrors
10878
+ };
10879
+ const backupPath = resolveBackupPath(absolutePath);
10880
+ await writeFile(backupPath, original, "utf-8");
10881
+ await writeFile(absolutePath, compressed, "utf-8");
10882
+ try {
10883
+ await recordAudit(kv, "compress", "mem::compress-file", [], {
10884
+ filePath: absolutePath,
10885
+ backupPath,
10886
+ originalChars: original.length,
10887
+ compressedChars: compressed.length
10888
+ });
10889
+ } catch {}
10890
+ return {
10891
+ success: true,
10892
+ filePath: absolutePath,
10893
+ backupPath,
10894
+ originalChars: original.length,
10895
+ compressedChars: compressed.length
10896
+ };
10897
+ });
10898
+ }
10899
+
10705
10900
  //#endregion
10706
10901
  //#region src/health/thresholds.ts
10707
10902
  const DEFAULTS = {
@@ -11082,11 +11277,25 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11082
11277
  status_code: 400,
11083
11278
  body: { error: "cwd must be a string" }
11084
11279
  };
11280
+ if (body.format !== void 0 && (typeof body.format !== "string" || ![
11281
+ "full",
11282
+ "compact",
11283
+ "narrative"
11284
+ ].includes(body.format.trim().toLowerCase()))) return {
11285
+ status_code: 400,
11286
+ body: { error: "format must be one of: full, compact, narrative" }
11287
+ };
11288
+ if (body.token_budget !== void 0 && (!Number.isInteger(body.token_budget) || body.token_budget < 1)) return {
11289
+ status_code: 400,
11290
+ body: { error: "token_budget must be a positive integer" }
11291
+ };
11085
11292
  const payload = {
11086
11293
  query: body.query.trim(),
11087
11294
  limit: body.limit,
11088
11295
  project: body.project,
11089
- cwd: body.cwd
11296
+ cwd: body.cwd,
11297
+ format: typeof body.format === "string" ? body.format.trim().toLowerCase() : void 0,
11298
+ token_budget: body.token_budget
11090
11299
  };
11091
11300
  return {
11092
11301
  status_code: 200,
@@ -11105,6 +11314,30 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
11105
11314
  middleware_function_ids: ["middleware::api-auth"]
11106
11315
  }
11107
11316
  });
11317
+ sdk.registerFunction("api::compress-file", async (req) => {
11318
+ const authErr = checkAuth(req, secret);
11319
+ if (authErr) return authErr;
11320
+ const filePath = asNonEmptyString$1((req.body ?? {}).filePath);
11321
+ if (!filePath) return {
11322
+ status_code: 400,
11323
+ body: { error: "filePath is required and must be a non-empty string" }
11324
+ };
11325
+ return {
11326
+ status_code: 200,
11327
+ body: await sdk.trigger({
11328
+ function_id: "mem::compress-file",
11329
+ payload: { filePath }
11330
+ })
11331
+ };
11332
+ });
11333
+ sdk.registerTrigger({
11334
+ type: "http",
11335
+ function_id: "api::compress-file",
11336
+ config: {
11337
+ api_path: "/agentmemory/compress-file",
11338
+ http_method: "POST"
11339
+ }
11340
+ });
11108
11341
  sdk.registerFunction("api::session::start", async (req) => {
11109
11342
  const body = req.body ?? {};
11110
11343
  const sessionId = asNonEmptyString$1(body.sessionId);
@@ -13609,13 +13842,46 @@ function registerMcpEndpoints(sdk, kv, secret) {
13609
13842
  status_code: 400,
13610
13843
  body: { error: "query is required for memory_recall" }
13611
13844
  };
13845
+ const format = typeof args.format === "string" ? args.format.trim().toLowerCase() : "full";
13846
+ if (![
13847
+ "full",
13848
+ "compact",
13849
+ "narrative"
13850
+ ].includes(format)) return {
13851
+ status_code: 400,
13852
+ body: { error: "format must be one of: full, compact, narrative" }
13853
+ };
13854
+ const tokenBudget = asNumber(args.token_budget);
13855
+ if (args.token_budget !== void 0 && (!Number.isInteger(tokenBudget) || (tokenBudget ?? 0) < 1)) return {
13856
+ status_code: 400,
13857
+ body: { error: "token_budget must be a positive integer" }
13858
+ };
13612
13859
  const result = await sdk.trigger({
13613
13860
  function_id: "mem::search",
13614
13861
  payload: {
13615
13862
  query: args.query,
13616
- limit: typeof args.limit === "number" ? args.limit : 10
13863
+ limit: typeof args.limit === "number" ? args.limit : 10,
13864
+ format,
13865
+ token_budget: tokenBudget
13617
13866
  }
13618
13867
  });
13868
+ return {
13869
+ status_code: 200,
13870
+ body: { content: [{
13871
+ type: "text",
13872
+ text: format === "narrative" && result && typeof result === "object" && "text" in result && typeof result.text === "string" ? result.text : JSON.stringify(result, null, 2)
13873
+ }] }
13874
+ };
13875
+ }
13876
+ case "memory_compress_file": {
13877
+ if (typeof args.filePath !== "string" || !args.filePath.trim()) return {
13878
+ status_code: 400,
13879
+ body: { error: "filePath is required for memory_compress_file" }
13880
+ };
13881
+ const result = await sdk.trigger({
13882
+ function_id: "mem::compress-file",
13883
+ payload: { filePath: args.filePath.trim() }
13884
+ });
13619
13885
  return {
13620
13886
  status_code: 200,
13621
13887
  body: { content: [{
@@ -15323,6 +15589,7 @@ async function main() {
15323
15589
  registerQueryExpansionFunction(sdk, provider);
15324
15590
  registerTemporalGraphFunctions(sdk, kv, provider);
15325
15591
  registerRetentionFunctions(sdk, kv);
15592
+ registerCompressFileFunction(sdk, kv, provider);
15326
15593
  console.log(`[agentmemory] v0.6 advanced retrieval: sliding-window, query-expansion, temporal-graph, retention-scoring`);
15327
15594
  console.log(`[agentmemory] Orchestration layer: actions, frontier, leases, routines, signals, checkpoints, flow-compress, mesh, branch-aware, sentinels, sketches, crystallize, diagnostics, facets`);
15328
15595
  const snapshotConfig = loadSnapshotConfig();
@@ -15362,7 +15629,7 @@ async function main() {
15362
15629
  }
15363
15630
  }
15364
15631
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
15365
- console.log(`[agentmemory] Endpoints: 103 REST + 43 MCP tools + 6 MCP resources + 3 MCP prompts`);
15632
+ console.log(`[agentmemory] Endpoints: 104 REST + 44 MCP tools + 6 MCP resources + 3 MCP prompts`);
15366
15633
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
15367
15634
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
15368
15635
  const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
@@ -15429,4 +15696,4 @@ main().catch((err) => {
15429
15696
 
15430
15697
  //#endregion
15431
15698
  export { };
15432
- //# sourceMappingURL=src-M6V9yZW5.mjs.map
15699
+ //# sourceMappingURL=src-68MXysnV.mjs.map