@amemhq/core 2.1.0 → 2.1.2

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.
@@ -9,7 +9,7 @@ import {
9
9
  resolveAliasRaw,
10
10
  scrollIdsRaw,
11
11
  switchToMigrated
12
- } from "./chunk-XEMQZNLD.js";
12
+ } from "./chunk-Z7G7KOJK.js";
13
13
 
14
14
  // src/cli-migrate.ts
15
15
  var USAGE = `amem-migrate \u2014 move a memory store onto a different embedding model
package/dist/index.cjs CHANGED
@@ -81,12 +81,17 @@ module.exports = __toCommonJS(index_exports);
81
81
  var os = __toESM(require("os"), 1);
82
82
  var path = __toESM(require("path"), 1);
83
83
  var _dataDir = process.env.AMEM_DATA_DIR || path.join(os.homedir(), ".amem");
84
+ var _warn = (msg) => console.warn(msg);
84
85
  function configure(opts) {
85
86
  if (opts.dataDir) _dataDir = opts.dataDir;
87
+ if (opts.warn) _warn = opts.warn;
86
88
  }
87
89
  function getDataDir() {
88
90
  return _dataDir;
89
91
  }
92
+ function warn(msg) {
93
+ _warn(msg);
94
+ }
90
95
 
91
96
  // src/embedding.ts
92
97
  var pipeline = null;
@@ -144,6 +149,12 @@ function applyModelPaths(env) {
144
149
  env.allowLocalModels = true;
145
150
  }
146
151
  }
152
+ function humanBytes(n) {
153
+ if (n >= 1e9) return `${(n / 1e9).toFixed(2)} GB`;
154
+ if (n >= 1e6) return `${(n / 1e6).toFixed(1)} MB`;
155
+ if (n >= 1e3) return `${Math.round(n / 1e3)} kB`;
156
+ return `${n} B`;
157
+ }
147
158
  function makeProgressReporter() {
148
159
  const lastPct = /* @__PURE__ */ new Map();
149
160
  return (e) => {
@@ -152,7 +163,7 @@ function makeProgressReporter() {
152
163
  const pct = Math.floor(e.progress / 10) * 10;
153
164
  if (lastPct.get(e.file) === pct) return;
154
165
  lastPct.set(e.file, pct);
155
- const size = e.total ? ` of ${(e.total / 1e9).toFixed(2)} GB` : "";
166
+ const size = e.total ? ` of ${humanBytes(e.total)}` : "";
156
167
  console.log(`[amem] downloading ${e.file}: ${pct}%${size}`);
157
168
  };
158
169
  }
@@ -379,7 +390,7 @@ async function ensureCollection(collectionName) {
379
390
  if (inUse !== null && inUse !== wanted) throw new MixedEmbeddingModelsError(col, wanted, inUse);
380
391
  pinEmbeddingModel(wanted);
381
392
  if (wanted === LEGACY_DEFAULT_EMBEDDING_MODEL) {
382
- console.warn(
393
+ warn(
383
394
  `[amem] "${col}" is on ${LEGACY_DEFAULT_EMBEDDING_MODEL} (${LEGACY_DEFAULT_DIM}-dim).
384
395
  [amem] ${DEFAULT_EMBEDDING_MODEL} reads 8192 tokens where that one stops at 128, so anything longer is being truncated before it reaches the vector.
385
396
  [amem] To move: npx --package=@amemhq/core amem-migrate --from-collection ${col}`
@@ -783,7 +794,7 @@ function makeCrud(collectionName, modeBIsolated = false) {
783
794
  })
784
795
  )
785
796
  ]).catch((err) => {
786
- console.error(`[amem] retrieval tracking patch failed: ${err.message}`);
797
+ warn(`[amem] retrieval tracking patch failed: ${err.message}`);
787
798
  });
788
799
  for (const r of queryResults) {
789
800
  r.note.retrieval_count = (r.note.retrieval_count || 0) + 1;
@@ -1150,7 +1161,7 @@ var _warned = /* @__PURE__ */ new Set();
1150
1161
  function warnOnce(key, message) {
1151
1162
  if (_warned.has(key)) return;
1152
1163
  _warned.add(key);
1153
- console.error(message);
1164
+ warn(message);
1154
1165
  }
1155
1166
  function resolveProvider(role = "fast") {
1156
1167
  const raw = role === "strong" ? process.env.AMEM_LLM_STRONG_PROVIDER || _override.strong?.provider || void 0 : void 0;
@@ -1222,9 +1233,11 @@ async function llmCall(prompt, maxTokens = 500, role = "fast") {
1222
1233
  const isThinking = model.includes("gemini") || model.includes("pro-agent");
1223
1234
  const effectiveMaxTokens = isThinking ? Math.max(maxTokens * 8, 4e3) : maxTokens;
1224
1235
  try {
1225
- return provider === "openai" ? await openaiCall(prompt, model, effectiveMaxTokens, baseURL) : await anthropicCall(prompt, model, effectiveMaxTokens, baseURL);
1236
+ const text = provider === "openai" ? await openaiCall(prompt, model, effectiveMaxTokens, baseURL) : await anthropicCall(prompt, model, effectiveMaxTokens, baseURL);
1237
+ if (text === null) warn(`[amem] ${provider} answered with no text (model ${model})`);
1238
+ return text;
1226
1239
  } catch (e) {
1227
- console.error(`[amem] LLM call failed: ${e.message}`);
1240
+ warn(`[amem] LLM call failed: ${e.message}`);
1228
1241
  return null;
1229
1242
  }
1230
1243
  }
@@ -1323,7 +1336,8 @@ confidence guide (Story 27):
1323
1336
 
1324
1337
  Text: ${content}`;
1325
1338
  const raw = await llmCall(prompt, 400);
1326
- if (!raw)
1339
+ if (!raw) {
1340
+ warn("[amem] note construction got nothing back; storing with no keywords, tags or context");
1327
1341
  return {
1328
1342
  keywords: [],
1329
1343
  tags: [],
@@ -1333,6 +1347,7 @@ Text: ${content}`;
1333
1347
  topics: [],
1334
1348
  confidence: "medium"
1335
1349
  };
1350
+ }
1336
1351
  try {
1337
1352
  const data = parseJsonLoose(raw);
1338
1353
  const rawCategory = typeof data.category === "string" ? data.category : "General";
@@ -1351,7 +1366,7 @@ Text: ${content}`;
1351
1366
  confidence
1352
1367
  };
1353
1368
  } catch (e) {
1354
- console.error(`[amem] Note construction parse failed: ${e.message}`);
1369
+ warn(`[amem] Note construction parse failed: ${e.message}`);
1355
1370
  return {
1356
1371
  keywords: [],
1357
1372
  tags: [],
@@ -1380,9 +1395,15 @@ async function llmCrudDecision(userText, assistantText, existingMemories) {
1380
1395
  const raw = await llmCall(prompt, 400, resolveCrudRole());
1381
1396
  if (!raw) return [];
1382
1397
  const match = stripReasoning(raw).match(/\[.*\]/s);
1383
- if (!match) return [];
1398
+ if (!match) {
1399
+ warn("[amem] llmCrudDecision found no array in the response; nothing from this turn is stored");
1400
+ return [];
1401
+ }
1384
1402
  const parsed = JSON.parse(match[0]);
1385
- if (!Array.isArray(parsed)) return [];
1403
+ if (!Array.isArray(parsed)) {
1404
+ warn("[amem] llmCrudDecision parsed a non-array; nothing from this turn is stored");
1405
+ return [];
1406
+ }
1386
1407
  const ops = [];
1387
1408
  for (const item of parsed) {
1388
1409
  if (!item || typeof item !== "object") continue;
@@ -1401,7 +1422,7 @@ async function llmCrudDecision(userText, assistantText, existingMemories) {
1401
1422
  }
1402
1423
  return ops.slice(0, 3);
1403
1424
  } catch (e) {
1404
- console.error(`[amem] llmCrudDecision failed: ${e.message}`);
1425
+ warn(`[amem] llmCrudDecision failed: ${e.message}`);
1405
1426
  return [];
1406
1427
  }
1407
1428
  }
@@ -1411,13 +1432,16 @@ async function llmShouldMerge(contentA, contentB) {
1411
1432
  if (!raw) return { shouldMerge: false };
1412
1433
  try {
1413
1434
  const data = parseJsonLoose(raw);
1414
- if (typeof data.shouldMerge !== "boolean") return { shouldMerge: false };
1435
+ if (typeof data.shouldMerge !== "boolean") {
1436
+ warn("[amem] llmShouldMerge got no boolean verdict; treating the pair as distinct");
1437
+ return { shouldMerge: false };
1438
+ }
1415
1439
  if (data.shouldMerge && typeof data.merged === "string") {
1416
1440
  return { shouldMerge: true, merged: data.merged };
1417
1441
  }
1418
1442
  return { shouldMerge: false };
1419
1443
  } catch (e) {
1420
- console.error(`[amem] llmShouldMerge parse failed: ${e.message}`);
1444
+ warn(`[amem] llmShouldMerge parse failed: ${e.message}`);
1421
1445
  return { shouldMerge: false };
1422
1446
  }
1423
1447
  }
@@ -1425,7 +1449,10 @@ var VALID_EVOLUTION_TYPES = /* @__PURE__ */ new Set(["EVOLVE", "CONFLICT", "EXPA
1425
1449
  async function llmEvolutionJudge(oldContent, newContent) {
1426
1450
  const prompt = t.evolutionJudge(oldContent, newContent);
1427
1451
  const raw = await llmCall(prompt, 300, "strong");
1428
- if (!raw) return { type: "NEW" };
1452
+ if (!raw) {
1453
+ warn("[amem] evolution judge got nothing back; defaulting the pair to NEW");
1454
+ return { type: "NEW" };
1455
+ }
1429
1456
  try {
1430
1457
  const data = parseJsonLoose(raw);
1431
1458
  const type = VALID_EVOLUTION_TYPES.has(data.type) ? data.type : "NEW";
@@ -1434,7 +1461,7 @@ async function llmEvolutionJudge(oldContent, newContent) {
1434
1461
  mergedContent: typeof data.mergedContent === "string" ? data.mergedContent : void 0
1435
1462
  };
1436
1463
  } catch (e) {
1437
- console.error(`[amem] llmEvolutionJudge parse failed: ${e.message}`);
1464
+ warn(`[amem] llmEvolutionJudge parse failed: ${e.message}`);
1438
1465
  return { type: "NEW" };
1439
1466
  }
1440
1467
  }
@@ -1473,7 +1500,7 @@ ${linkedStr}`;
1473
1500
  tagsToUpdate: Array.isArray(data.tags_to_update) ? data.tags_to_update.map(String) : []
1474
1501
  };
1475
1502
  } catch (e) {
1476
- console.error(`[amem] Evolution parse failed: ${e.message}`);
1503
+ warn(`[amem] Evolution parse failed: ${e.message}`);
1477
1504
  return { tags: null, context: null, shouldStrengthen: false, suggestedConnections: [], tagsToUpdate: [] };
1478
1505
  }
1479
1506
  }
@@ -1485,9 +1512,15 @@ async function llmConflictScan(contents) {
1485
1512
  if (!raw) return [];
1486
1513
  const cleaned = stripReasoning(raw);
1487
1514
  const match = cleaned.match(/\[[\s\S]*\]/);
1488
- if (!match) return [];
1515
+ if (!match) {
1516
+ warn("[amem] llmConflictScan found no array in the response; this sweep reports no pairs");
1517
+ return [];
1518
+ }
1489
1519
  const parsed = JSON.parse(match[0]);
1490
- if (!Array.isArray(parsed)) return [];
1520
+ if (!Array.isArray(parsed)) {
1521
+ warn("[amem] llmConflictScan parsed a non-array; this sweep reports no pairs");
1522
+ return [];
1523
+ }
1491
1524
  const pairs = [];
1492
1525
  const seen = /* @__PURE__ */ new Set();
1493
1526
  for (const item of parsed) {
@@ -1511,7 +1544,7 @@ async function llmConflictScan(contents) {
1511
1544
  }
1512
1545
  return pairs;
1513
1546
  } catch (e) {
1514
- console.error(`[amem] llmConflictScan failed: ${e.message}`);
1547
+ warn(`[amem] llmConflictScan failed: ${e.message}`);
1515
1548
  return [];
1516
1549
  }
1517
1550
  }
@@ -1825,7 +1858,7 @@ async function addMemory(content, agentId = "main", opts) {
1825
1858
  }
1826
1859
  }
1827
1860
  } catch (e) {
1828
- console.error(`[warn] Link/Evolution phase failed: ${e.message}`);
1861
+ warn(`[amem] link/evolution phase failed: ${e.message}`);
1829
1862
  }
1830
1863
  console.log(`[done] Note added: ${note.id}`);
1831
1864
  return note.id;
@@ -1950,7 +1983,12 @@ async function searchMemory(query, topK = 5, agentId = "main", opts) {
1950
1983
  keywords: note.keywords,
1951
1984
  links: note.links,
1952
1985
  timestamp: note.timestamp,
1953
- similarity: embSimMap.get(id) ?? bfsSimMap.get(id) ?? 0,
1986
+ // Neither map covers a note that got here on BM25 alone: it was never in
1987
+ // the dense results, and it was not expanded into. That is a real cosine
1988
+ // nobody had measured, not a zero — and reporting 0 made a lexical match
1989
+ // look like the least relevant row in the list. Both vectors are already
1990
+ // in hand, so measuring it is one dot product.
1991
+ similarity: embSimMap.get(id) ?? bfsSimMap.get(id) ?? cosineSimilarity(queryEmbedding, note.embedding),
1954
1992
  rrf: rrfMap.get(id) ?? 0,
1955
1993
  via,
1956
1994
  topics: note.topics ?? [],
@@ -2086,8 +2124,8 @@ async function consolidateMemories(agentId, logger, storageCtx) {
2086
2124
  const ctx = storageCtx ?? defaultCtx();
2087
2125
  const log = {
2088
2126
  info: (msg) => logger ? logger.info(msg) : console.log(msg),
2089
- warn: (msg) => logger ? logger.warn(msg) : console.warn(msg),
2090
- error: (msg) => logger ? logger.error(msg) : console.error(msg)
2127
+ warn: (msg) => logger ? logger.warn(msg) : warn(msg),
2128
+ error: (msg) => logger ? logger.error(msg) : warn(msg)
2091
2129
  };
2092
2130
  log.info(`[Consolidation] Starting consolidation for agentId: ${agentId}`);
2093
2131
  const rawNotes = await ctx.listNotes(agentId);
@@ -2463,7 +2501,7 @@ async function migrateCollection(opts) {
2463
2501
  const refreshFields = opts.refreshFields !== false;
2464
2502
  const dryRun = opts.dryRun !== false;
2465
2503
  const log = opts.logger?.info ?? ((m) => console.log(m));
2466
- const warn = opts.logger?.warn ?? ((m) => console.warn(m));
2504
+ const warn2 = opts.logger?.warn ?? warn;
2467
2505
  if (from === to) throw new Error(`migrate: source and target are the same collection ("${from}")`);
2468
2506
  const model = getEmbeddingModel();
2469
2507
  const targetDim = await getEmbeddingDim();
@@ -2531,7 +2569,7 @@ async function migrateCollection(opts) {
2531
2569
  if (!note.context) note.context = built.context;
2532
2570
  refreshed++;
2533
2571
  } catch (e) {
2534
- warn(`[migrate] re-extract failed for ${note.id.slice(0, 8)} \u2014 keeping as-is: ${e.message}`);
2572
+ warn2(`[migrate] re-extract failed for ${note.id.slice(0, 8)} \u2014 keeping as-is: ${e.message}`);
2535
2573
  }
2536
2574
  }
2537
2575
  const point = noteToPoint({ ...note, embedding: await encode(buildEmbedText(note)) });
@@ -2544,7 +2582,7 @@ async function migrateCollection(opts) {
2544
2582
  await flush();
2545
2583
  const finalCount = await countPointsRaw(to);
2546
2584
  if (finalCount !== notes.length) {
2547
- warn(`[migrate] target holds ${finalCount} point(s) but the source had ${notes.length} \u2014 check before switching`);
2585
+ warn2(`[migrate] target holds ${finalCount} point(s) but the source had ${notes.length} \u2014 check before switching`);
2548
2586
  }
2549
2587
  log(`[migrate] done: ${migrated} written, ${alreadyDone.size} already present, ${refreshed} re-extracted.`);
2550
2588
  return {