ziiagentmemory 0.1.0 → 0.3.0

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/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as runAdapter, i as resolveAdapter, r as knownAgents, t as ADAPTERS } from "./connect-JnsJXc68.mjs";
3
- import { i as generateId } from "./schema-CNiCWzV9.mjs";
3
+ import { i as generateId } from "./schema-BQLY6d8C.mjs";
4
4
  import { r as setBootVerbose } from "./logger-BEB7pCI9.mjs";
5
5
  import { i as VERSION, n as getAllTools, t as ESSENTIAL_TOOLS } from "./tools-registry-DCt2KAWA.mjs";
6
6
  import { execFileSync, spawn, spawnSync } from "node:child_process";
@@ -1552,7 +1552,7 @@ async function main() {
1552
1552
  if (firstRun || IS_RESET) await runOnboarding();
1553
1553
  if (skipEngine) {
1554
1554
  if (IS_VERBOSE) p.log.info("Skipping engine check (--no-engine)");
1555
- await import("./src-Cr3pH_uH.mjs");
1555
+ await import("./src-Dl5_tJgD.mjs");
1556
1556
  if (await waitForAgentmemoryReady(15e3)) {
1557
1557
  const consoleState = await ensureIiiConsole();
1558
1558
  await maybeOfferGlobalInstall();
@@ -1567,7 +1567,7 @@ async function main() {
1567
1567
  const detected = attachedBin ? iiiBinVersion(attachedBin) : null;
1568
1568
  if (detected === IIPINNED_VERSION) {
1569
1569
  adoptRunningEngine();
1570
- await import("./src-Cr3pH_uH.mjs");
1570
+ await import("./src-Dl5_tJgD.mjs");
1571
1571
  if (await waitForAgentmemoryReady(15e3)) {
1572
1572
  const consoleState = await ensureIiiConsole();
1573
1573
  await maybeOfferGlobalInstall();
@@ -1638,7 +1638,7 @@ async function main() {
1638
1638
  process.exit(1);
1639
1639
  }
1640
1640
  s.stop(c.ok("iii-engine is ready"));
1641
- await import("./src-Cr3pH_uH.mjs");
1641
+ await import("./src-Dl5_tJgD.mjs");
1642
1642
  if (await waitForAgentmemoryReady(15e3)) {
1643
1643
  const consoleState = await ensureIiiConsole();
1644
1644
  await maybeOfferGlobalInstall();
@@ -2286,7 +2286,7 @@ async function startServerForDemo() {
2286
2286
  process.exit(1);
2287
2287
  }
2288
2288
  }
2289
- await import("./src-Cr3pH_uH.mjs");
2289
+ await import("./src-Dl5_tJgD.mjs");
2290
2290
  if (!await waitForAgentmemoryReady(15e3)) {
2291
2291
  p.log.error("agentmemory worker did not become ready within 15s.");
2292
2292
  process.exit(1);
@@ -2619,7 +2619,7 @@ async function runStop() {
2619
2619
  p.outro("Stopped. Memories persisted to disk; restart anytime with: npx @agentmemory/agentmemory");
2620
2620
  }
2621
2621
  async function runMcp() {
2622
- await import("./standalone-C6KzNkt5.mjs");
2622
+ await import("./standalone-CSnfnb1m.mjs");
2623
2623
  }
2624
2624
  async function runConnectCmd() {
2625
2625
  const { runConnect } = await import("./connect-JnsJXc68.mjs").then((n) => n.n);
@@ -1,4 +1,4 @@
1
- import { t as KV } from "./schema-CNiCWzV9.mjs";
1
+ import { t as KV } from "./schema-BQLY6d8C.mjs";
2
2
  import { r as deleteImage, s as touchImage, t as withKeyedLock } from "./keyed-mutex-DTHtcZrt.mjs";
3
3
  import { TriggerAction } from "iii-sdk";
4
4
  //#region src/functions/image-refs.ts
@@ -31,4 +31,4 @@ async function decrementImageRef(kv, sdk, filePath) {
31
31
  //#endregion
32
32
  export { decrementImageRef, getImageRefCount, incrementImageRef };
33
33
 
34
- //# sourceMappingURL=image-refs-C2YU7BIm.mjs.map
34
+ //# sourceMappingURL=image-refs-Cc7-ua5j.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"image-refs-C2YU7BIm.mjs","names":[],"sources":["../src/functions/image-refs.ts"],"sourcesContent":["import { TriggerAction, type ISdk } from \"iii-sdk\";\r\nimport { KV } from \"../state/schema.js\";\r\nimport { StateKV } from \"../state/kv.js\";\r\nimport { deleteImage, touchImage } from \"../utils/image-store.js\";\r\nimport { withKeyedLock } from \"../state/keyed-mutex.js\";\r\n\r\nexport async function getImageRefCount(kv: StateKV, filePath: string): Promise<number> {\r\n const count = await kv.get<number>(KV.imageRefs, filePath);\r\n return count ? Number(count) : 0;\r\n}\r\n\r\nexport async function incrementImageRef(kv: StateKV, filePath: string): Promise<void> {\r\n return withKeyedLock(`imgRef:${filePath}`, async () => {\r\n const current = await getImageRefCount(kv, filePath);\r\n await kv.set(KV.imageRefs, filePath, current + 1);\r\n await touchImage(filePath);\r\n });\r\n}\r\n\r\nexport async function decrementImageRef(kv: StateKV, sdk: ISdk, filePath: string): Promise<void> {\r\n return withKeyedLock(`imgRef:${filePath}`, async () => {\r\n const current = await getImageRefCount(kv, filePath);\r\n if (current <= 1) {\r\n await kv.delete(KV.imageEmbeddings, filePath);\r\n await kv.delete(KV.imageRefs, filePath);\r\n const { deletedBytes } = await deleteImage(filePath);\r\n if (deletedBytes > 0) {\r\n sdk.trigger({\r\n function_id: \"mem::disk-size-delta\",\r\n payload: { deltaBytes: -deletedBytes },\r\n action: TriggerAction.Void(),\r\n });\r\n }\r\n } else {\r\n await kv.set(KV.imageRefs, filePath, current - 1);\r\n }\r\n });\r\n}\r\n"],"mappings":";;;;AAMA,eAAsB,iBAAiB,IAAa,UAAmC;CACrF,MAAM,QAAQ,MAAM,GAAG,IAAY,GAAG,WAAW,SAAS;AAC1D,QAAO,QAAQ,OAAO,MAAM,GAAG;;AAGjC,eAAsB,kBAAkB,IAAa,UAAiC;AACpF,QAAO,cAAc,UAAU,YAAY,YAAY;EACrD,MAAM,UAAU,MAAM,iBAAiB,IAAI,SAAS;AACpD,QAAM,GAAG,IAAI,GAAG,WAAW,UAAU,UAAU,EAAE;AACjD,QAAM,WAAW,SAAS;GAC1B;;AAGJ,eAAsB,kBAAkB,IAAa,KAAW,UAAiC;AAC/F,QAAO,cAAc,UAAU,YAAY,YAAY;EACrD,MAAM,UAAU,MAAM,iBAAiB,IAAI,SAAS;AACpD,MAAI,WAAW,GAAG;AAChB,SAAM,GAAG,OAAO,GAAG,iBAAiB,SAAS;AAC7C,SAAM,GAAG,OAAO,GAAG,WAAW,SAAS;GACvC,MAAM,EAAE,iBAAiB,MAAM,YAAY,SAAS;AACpD,OAAI,eAAe,EACjB,KAAI,QAAQ;IACV,aAAa;IACb,SAAS,EAAE,YAAY,CAAC,cAAc;IACtC,QAAQ,cAAc,MAAM;IAC7B,CAAC;QAGJ,OAAM,GAAG,IAAI,GAAG,WAAW,UAAU,UAAU,EAAE;GAEnD"}
1
+ {"version":3,"file":"image-refs-Cc7-ua5j.mjs","names":[],"sources":["../src/functions/image-refs.ts"],"sourcesContent":["import { TriggerAction, type ISdk } from \"iii-sdk\";\r\nimport { KV } from \"../state/schema.js\";\r\nimport { StateKV } from \"../state/kv.js\";\r\nimport { deleteImage, touchImage } from \"../utils/image-store.js\";\r\nimport { withKeyedLock } from \"../state/keyed-mutex.js\";\r\n\r\nexport async function getImageRefCount(kv: StateKV, filePath: string): Promise<number> {\r\n const count = await kv.get<number>(KV.imageRefs, filePath);\r\n return count ? Number(count) : 0;\r\n}\r\n\r\nexport async function incrementImageRef(kv: StateKV, filePath: string): Promise<void> {\r\n return withKeyedLock(`imgRef:${filePath}`, async () => {\r\n const current = await getImageRefCount(kv, filePath);\r\n await kv.set(KV.imageRefs, filePath, current + 1);\r\n await touchImage(filePath);\r\n });\r\n}\r\n\r\nexport async function decrementImageRef(kv: StateKV, sdk: ISdk, filePath: string): Promise<void> {\r\n return withKeyedLock(`imgRef:${filePath}`, async () => {\r\n const current = await getImageRefCount(kv, filePath);\r\n if (current <= 1) {\r\n await kv.delete(KV.imageEmbeddings, filePath);\r\n await kv.delete(KV.imageRefs, filePath);\r\n const { deletedBytes } = await deleteImage(filePath);\r\n if (deletedBytes > 0) {\r\n sdk.trigger({\r\n function_id: \"mem::disk-size-delta\",\r\n payload: { deltaBytes: -deletedBytes },\r\n action: TriggerAction.Void(),\r\n });\r\n }\r\n } else {\r\n await kv.set(KV.imageRefs, filePath, current - 1);\r\n }\r\n });\r\n}\r\n"],"mappings":";;;;AAMA,eAAsB,iBAAiB,IAAa,UAAmC;CACrF,MAAM,QAAQ,MAAM,GAAG,IAAY,GAAG,WAAW,SAAS;AAC1D,QAAO,QAAQ,OAAO,MAAM,GAAG;;AAGjC,eAAsB,kBAAkB,IAAa,UAAiC;AACpF,QAAO,cAAc,UAAU,YAAY,YAAY;EACrD,MAAM,UAAU,MAAM,iBAAiB,IAAI,SAAS;AACpD,QAAM,GAAG,IAAI,GAAG,WAAW,UAAU,UAAU,EAAE;AACjD,QAAM,WAAW,SAAS;GAC1B;;AAGJ,eAAsB,kBAAkB,IAAa,KAAW,UAAiC;AAC/F,QAAO,cAAc,UAAU,YAAY,YAAY;EACrD,MAAM,UAAU,MAAM,iBAAiB,IAAI,SAAS;AACpD,MAAI,WAAW,GAAG;AAChB,SAAM,GAAG,OAAO,GAAG,iBAAiB,SAAS;AAC7C,SAAM,GAAG,OAAO,GAAG,WAAW,SAAS;GACvC,MAAM,EAAE,iBAAiB,MAAM,YAAY,SAAS;AACpD,OAAI,eAAe,EACjB,KAAI,QAAQ;IACV,aAAa;IACb,SAAS,EAAE,YAAY,CAAC,cAAc;IACtC,QAAQ,cAAc,MAAM;IAC7B,CAAC;QAGJ,OAAM,GAAG,IAAI,GAAG,WAAW,UAAU,UAAU,EAAE;GAEnD"}
package/dist/index.mjs CHANGED
@@ -855,25 +855,25 @@ function l2Normalize(vec) {
855
855
  }
856
856
  //#endregion
857
857
  //#region src/providers/embedding/openai.ts
858
- const DEFAULT_MODEL$1 = "text-embedding-3-small";
858
+ const DEFAULT_MODEL$2 = "text-embedding-3-small";
859
859
  /**
860
860
  * Known OpenAI embedding model dimensions. Extend as new models ship.
861
861
  * Override in any case via OPENAI_EMBEDDING_DIMENSIONS for custom or
862
862
  * self-hosted OpenAI-compatible endpoints returning non-standard sizes.
863
863
  */
864
- const MODEL_DIMENSIONS = {
864
+ const MODEL_DIMENSIONS$1 = {
865
865
  "text-embedding-3-small": 1536,
866
866
  "text-embedding-3-large": 3072,
867
867
  "text-embedding-ada-002": 1536
868
868
  };
869
- const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL$1] ?? 1536;
870
- function resolveDimensions(model, override) {
869
+ const DEFAULT_DIMENSIONS$1 = MODEL_DIMENSIONS$1[DEFAULT_MODEL$2] ?? 1536;
870
+ function resolveDimensions$1(model, override) {
871
871
  if (override !== void 0 && override.trim().length > 0) {
872
872
  const parsed = parseInt(override, 10);
873
873
  if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`OPENAI_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`);
874
874
  return parsed;
875
875
  }
876
- return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS;
876
+ return MODEL_DIMENSIONS$1[model] ?? DEFAULT_DIMENSIONS$1;
877
877
  }
878
878
  /**
879
879
  * OpenAI-compatible embedding provider.
@@ -923,8 +923,8 @@ var OpenAIEmbeddingProvider = class {
923
923
  this.apiKey = apiKey || getEnvVar("OPENAI_EMBEDDING_API_KEY") || getEnvVar("OPENAI_API_KEY") || "";
924
924
  if (!this.apiKey) throw new Error("API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)");
925
925
  this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"));
926
- this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
927
- this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
926
+ this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$2;
927
+ this.dimensions = resolveDimensions$1(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
928
928
  this.isAzure = detectAzure(this.baseUrl);
929
929
  this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
930
930
  }
@@ -1021,15 +1021,36 @@ var CohereEmbeddingProvider = class {
1021
1021
  //#endregion
1022
1022
  //#region src/providers/embedding/openrouter.ts
1023
1023
  const API_URL = "https://openrouter.ai/api/v1/embeddings";
1024
+ /**
1025
+ * Known OpenRouter embedding model dimensions. Extend as new models ship.
1026
+ * Override via OPENROUTER_EMBEDDING_DIMENSIONS for models not listed here
1027
+ * or custom OpenAI-compatible endpoints returning non-standard sizes.
1028
+ */
1029
+ const MODEL_DIMENSIONS = {
1030
+ "openai/text-embedding-3-small": 1536,
1031
+ "openai/text-embedding-3-large": 3072,
1032
+ "openai/text-embedding-ada-002": 1536
1033
+ };
1034
+ const DEFAULT_MODEL$1 = "openai/text-embedding-3-small";
1035
+ const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL$1] ?? 1536;
1036
+ function resolveDimensions(model, override) {
1037
+ if (override !== void 0 && override.trim().length > 0) {
1038
+ const parsed = parseInt(override, 10);
1039
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`OPENROUTER_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`);
1040
+ return parsed;
1041
+ }
1042
+ return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS;
1043
+ }
1024
1044
  var OpenRouterEmbeddingProvider = class {
1025
1045
  name = "openrouter";
1026
- dimensions = 1536;
1046
+ dimensions;
1027
1047
  apiKey;
1028
1048
  model;
1029
1049
  constructor(apiKey) {
1030
1050
  this.apiKey = apiKey || getEnvVar("OPENROUTER_API_KEY") || "";
1031
1051
  if (!this.apiKey) throw new Error("OPENROUTER_API_KEY is required");
1032
- this.model = getEnvVar("OPENROUTER_EMBEDDING_MODEL") || "openai/text-embedding-3-small";
1052
+ this.model = getEnvVar("OPENROUTER_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
1053
+ this.dimensions = resolveDimensions(this.model, getEnvVar("OPENROUTER_EMBEDDING_DIMENSIONS"));
1033
1054
  }
1034
1055
  async embed(text) {
1035
1056
  const [result] = await this.embedBatch([text]);
@@ -1301,6 +1322,109 @@ var StateKV = class {
1301
1322
  }
1302
1323
  };
1303
1324
  //#endregion
1325
+ //#region src/state/cjk-segmenter.ts
1326
+ const cjkRequire = createRequire(import.meta.url);
1327
+ const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
1328
+ const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u;
1329
+ const HANGUL_RE = /\p{Script=Hangul}/u;
1330
+ const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
1331
+ const HANGUL_BLOCK_RE = /[가-힯]+/g;
1332
+ const hintShown = /* @__PURE__ */ new Set();
1333
+ function hasCjk(text) {
1334
+ return CJK_RE.test(text);
1335
+ }
1336
+ function showHintOnce(key, message) {
1337
+ if (hintShown.has(key)) return;
1338
+ hintShown.add(key);
1339
+ if (typeof process !== "undefined" && process.stderr?.write) process.stderr.write(`agentmemory: ${message}\n`);
1340
+ }
1341
+ let jiebaInstance = null;
1342
+ let jiebaLoaded = false;
1343
+ function getJieba() {
1344
+ if (jiebaLoaded) return jiebaInstance;
1345
+ jiebaLoaded = true;
1346
+ try {
1347
+ const mod = cjkRequire("@node-rs/jieba");
1348
+ try {
1349
+ const dictMod = cjkRequire("@node-rs/jieba/dict");
1350
+ jiebaInstance = mod.Jieba.withDict(dictMod.dict);
1351
+ } catch {
1352
+ jiebaInstance = new mod.Jieba();
1353
+ }
1354
+ return jiebaInstance;
1355
+ } catch {
1356
+ showHintOnce("jieba", "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization");
1357
+ return null;
1358
+ }
1359
+ }
1360
+ let jaSegmenterInstance = null;
1361
+ let jaSegmenterLoaded = false;
1362
+ function getJaSegmenter() {
1363
+ if (jaSegmenterLoaded) return jaSegmenterInstance;
1364
+ jaSegmenterLoaded = true;
1365
+ try {
1366
+ jaSegmenterInstance = new (cjkRequire("tiny-segmenter"))();
1367
+ return jaSegmenterInstance;
1368
+ } catch {
1369
+ showHintOnce("tiny-segmenter", "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization");
1370
+ return null;
1371
+ }
1372
+ }
1373
+ function cleanTokens(tokens) {
1374
+ const out = [];
1375
+ for (const t of tokens) {
1376
+ const trimmed = t.trim();
1377
+ if (trimmed) out.push(trimmed);
1378
+ }
1379
+ return out;
1380
+ }
1381
+ function segmentHan(text) {
1382
+ const j = getJieba();
1383
+ if (!j) return [text];
1384
+ try {
1385
+ return cleanTokens(j.cut(text, true));
1386
+ } catch {
1387
+ return [text];
1388
+ }
1389
+ }
1390
+ function segmentKana(text) {
1391
+ const s = getJaSegmenter();
1392
+ if (!s) return [text];
1393
+ try {
1394
+ return cleanTokens(s.segment(text));
1395
+ } catch {
1396
+ return [text];
1397
+ }
1398
+ }
1399
+ function segmentHangul(text) {
1400
+ const out = [];
1401
+ for (const m of text.matchAll(HANGUL_BLOCK_RE)) if (m[0]) out.push(m[0]);
1402
+ return out;
1403
+ }
1404
+ function segmentCjk(text) {
1405
+ if (!hasCjk(text)) return [text];
1406
+ const out = [];
1407
+ let cursor = 0;
1408
+ for (const m of text.matchAll(CJK_RUN_RE)) {
1409
+ const start = m.index ?? 0;
1410
+ const run = m[0];
1411
+ const end = start + run.length;
1412
+ if (start > cursor) {
1413
+ const piece = text.slice(cursor, start).trim();
1414
+ if (piece) out.push(piece);
1415
+ }
1416
+ if (HANGUL_RE.test(run)) out.push(...segmentHangul(run));
1417
+ else if (KANA_RE.test(run)) out.push(...segmentKana(run));
1418
+ else out.push(...segmentHan(run));
1419
+ cursor = end;
1420
+ }
1421
+ if (cursor < text.length) {
1422
+ const trailing = text.slice(cursor).trim();
1423
+ if (trailing) out.push(trailing);
1424
+ }
1425
+ return out;
1426
+ }
1427
+ //#endregion
1304
1428
  //#region src/state/schema.ts
1305
1429
  const KV = {
1306
1430
  sessions: "mem:sessions",
@@ -1365,9 +1489,33 @@ function generateId(prefix) {
1365
1489
  function fingerprintId(prefix, content) {
1366
1490
  return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
1367
1491
  }
1492
+ /**
1493
+ * CJK-aware tokenizer for Jaccard similarity.
1494
+ *
1495
+ * For English/whitespace-delimited text we keep the original behaviour:
1496
+ * split on whitespace, filter tokens longer than 2 characters.
1497
+ *
1498
+ * For CJK text (Chinese/Japanese/Korean) whitespace splitting produces
1499
+ * near-disjoint sets because CJK text is typically written without spaces.
1500
+ * We reuse the existing `segmentCjk` function and additionally emit
1501
+ * character bigrams from CJK runs so that no-space Chinese text still
1502
+ * produces useful overlap.
1503
+ */
1504
+ function tokenizeForJaccard(text) {
1505
+ if (!hasCjk(text)) return new Set(text.split(/\s+/).filter((t) => t.length > 2));
1506
+ const segments = segmentCjk(text);
1507
+ const tokens = /* @__PURE__ */ new Set();
1508
+ for (const seg of segments) {
1509
+ const cleaned = seg.replace(/[^\p{L}\p{N}]/gu, "").toLowerCase();
1510
+ if (cleaned.length > 2) tokens.add(cleaned);
1511
+ const cjkChars = seg.match(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu);
1512
+ if (cjkChars && cjkChars.length >= 2) for (let i = 0; i < cjkChars.length - 1; i++) tokens.add(cjkChars[i] + cjkChars[i + 1]);
1513
+ }
1514
+ return tokens;
1515
+ }
1368
1516
  function jaccardSimilarity(a, b) {
1369
- const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
1370
- const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
1517
+ const setA = tokenizeForJaccard(a);
1518
+ const setB = tokenizeForJaccard(b);
1371
1519
  if (setA.size === 0 && setB.size === 0) return 1;
1372
1520
  if (setA.size === 0 || setB.size === 0) return 0;
1373
1521
  let intersection = 0;
@@ -2429,109 +2577,6 @@ function getSynonyms(stemmedTerm) {
2429
2577
  return syns ? [...syns] : [];
2430
2578
  }
2431
2579
  //#endregion
2432
- //#region src/state/cjk-segmenter.ts
2433
- const cjkRequire = createRequire(import.meta.url);
2434
- const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
2435
- const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u;
2436
- const HANGUL_RE = /\p{Script=Hangul}/u;
2437
- const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
2438
- const HANGUL_BLOCK_RE = /[가-힯]+/g;
2439
- const hintShown = /* @__PURE__ */ new Set();
2440
- function hasCjk(text) {
2441
- return CJK_RE.test(text);
2442
- }
2443
- function showHintOnce(key, message) {
2444
- if (hintShown.has(key)) return;
2445
- hintShown.add(key);
2446
- if (typeof process !== "undefined" && process.stderr?.write) process.stderr.write(`agentmemory: ${message}\n`);
2447
- }
2448
- let jiebaInstance = null;
2449
- let jiebaLoaded = false;
2450
- function getJieba() {
2451
- if (jiebaLoaded) return jiebaInstance;
2452
- jiebaLoaded = true;
2453
- try {
2454
- const mod = cjkRequire("@node-rs/jieba");
2455
- try {
2456
- const dictMod = cjkRequire("@node-rs/jieba/dict");
2457
- jiebaInstance = mod.Jieba.withDict(dictMod.dict);
2458
- } catch {
2459
- jiebaInstance = new mod.Jieba();
2460
- }
2461
- return jiebaInstance;
2462
- } catch {
2463
- showHintOnce("jieba", "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization");
2464
- return null;
2465
- }
2466
- }
2467
- let jaSegmenterInstance = null;
2468
- let jaSegmenterLoaded = false;
2469
- function getJaSegmenter() {
2470
- if (jaSegmenterLoaded) return jaSegmenterInstance;
2471
- jaSegmenterLoaded = true;
2472
- try {
2473
- jaSegmenterInstance = new (cjkRequire("tiny-segmenter"))();
2474
- return jaSegmenterInstance;
2475
- } catch {
2476
- showHintOnce("tiny-segmenter", "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization");
2477
- return null;
2478
- }
2479
- }
2480
- function cleanTokens(tokens) {
2481
- const out = [];
2482
- for (const t of tokens) {
2483
- const trimmed = t.trim();
2484
- if (trimmed) out.push(trimmed);
2485
- }
2486
- return out;
2487
- }
2488
- function segmentHan(text) {
2489
- const j = getJieba();
2490
- if (!j) return [text];
2491
- try {
2492
- return cleanTokens(j.cut(text, true));
2493
- } catch {
2494
- return [text];
2495
- }
2496
- }
2497
- function segmentKana(text) {
2498
- const s = getJaSegmenter();
2499
- if (!s) return [text];
2500
- try {
2501
- return cleanTokens(s.segment(text));
2502
- } catch {
2503
- return [text];
2504
- }
2505
- }
2506
- function segmentHangul(text) {
2507
- const out = [];
2508
- for (const m of text.matchAll(HANGUL_BLOCK_RE)) if (m[0]) out.push(m[0]);
2509
- return out;
2510
- }
2511
- function segmentCjk(text) {
2512
- if (!hasCjk(text)) return [text];
2513
- const out = [];
2514
- let cursor = 0;
2515
- for (const m of text.matchAll(CJK_RUN_RE)) {
2516
- const start = m.index ?? 0;
2517
- const run = m[0];
2518
- const end = start + run.length;
2519
- if (start > cursor) {
2520
- const piece = text.slice(cursor, start).trim();
2521
- if (piece) out.push(piece);
2522
- }
2523
- if (HANGUL_RE.test(run)) out.push(...segmentHangul(run));
2524
- else if (KANA_RE.test(run)) out.push(...segmentKana(run));
2525
- else out.push(...segmentHan(run));
2526
- cursor = end;
2527
- }
2528
- if (cursor < text.length) {
2529
- const trailing = text.slice(cursor).trim();
2530
- if (trailing) out.push(trailing);
2531
- }
2532
- return out;
2533
- }
2534
- //#endregion
2535
2580
  //#region src/state/search-index.ts
2536
2581
  var SearchIndex = class SearchIndex {
2537
2582
  entries = /* @__PURE__ */ new Map();
@@ -5382,7 +5427,7 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
5382
5427
  error: "no_observations"
5383
5428
  };
5384
5429
  }
5385
- if (provider.name === "noop") {
5430
+ if (provider.name === "noop" || provider.name === "resilient(noop)") {
5386
5431
  logger.info("Summarize skipped — no LLM provider configured", { sessionId });
5387
5432
  return {
5388
5433
  success: false,
@@ -5993,6 +6038,34 @@ function registerPatternsFunction(sdk, kv) {
5993
6038
  });
5994
6039
  }
5995
6040
  //#endregion
6041
+ //#region src/state/memory-dedup.ts
6042
+ /**
6043
+ * Negation guard for memory dedup.
6044
+ *
6045
+ * Prevents "do X" from superseding "do not X" (and vice versa) even when
6046
+ * the two strings share enough tokens to exceed the Jaccard threshold.
6047
+ *
6048
+ * The guard is deliberately narrow and conservative:
6049
+ * - Only triggers when one side has a negation marker and the other does not.
6050
+ * - Supports English ("not", "never", "don't", "do not") and CJK
6051
+ * ("不要", "别", "無", "なし", "않") markers.
6052
+ * - If BOTH sides have negation markers, they are treated as compatible
6053
+ * (both say "don't do X") and the guard does not fire.
6054
+ */
6055
+ const EN_NEGATION = /\b(not|never|don't|do not|cannot|should not|must not|no)\b/i;
6056
+ const CJK_NEGATION = /不要|别|勿|無|无|不|なし|않|안|못/i;
6057
+ function hasNegation(text) {
6058
+ return EN_NEGATION.test(text) || CJK_NEGATION.test(text);
6059
+ }
6060
+ /**
6061
+ * Returns true if the two texts appear to be in a negation conflict
6062
+ * (one says do X, the other says do NOT do X) and should NOT be superseded.
6063
+ */
6064
+ function isNegationConflict(a, b) {
6065
+ if (hasNegation(a) === hasNegation(b)) return false;
6066
+ return true;
6067
+ }
6068
+ //#endregion
5996
6069
  //#region src/functions/remember.ts
5997
6070
  function registerRememberFunction(sdk, kv) {
5998
6071
  sdk.registerFunction("mem::remember", async (data) => {
@@ -6031,7 +6104,7 @@ function registerRememberFunction(sdk, kv) {
6031
6104
  for (const existing of existingMemories) {
6032
6105
  if (existing.isLatest === false) continue;
6033
6106
  if (project && existing.project && existing.project !== project) continue;
6034
- if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
6107
+ if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7 && !isNegationConflict(lowerContent, existing.content.toLowerCase())) {
6035
6108
  supersededId = existing.id;
6036
6109
  supersededVersion = existing.version ?? 1;
6037
6110
  supersededMemory = existing;
@@ -15457,6 +15530,10 @@ function parseOptionalInt(raw) {
15457
15530
  const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
15458
15531
  return Number.isFinite(n) ? n : void 0;
15459
15532
  }
15533
+ function parsePositiveLimit(raw) {
15534
+ const n = parseOptionalInt(raw);
15535
+ return n !== void 0 && n > 0 ? n : void 0;
15536
+ }
15460
15537
  function checkAuth(req, secret) {
15461
15538
  if (!secret) return null;
15462
15539
  const auth = req.headers?.["authorization"] || req.headers?.["Authorization"];
@@ -15845,11 +15922,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15845
15922
  if (authErr) return authErr;
15846
15923
  const sessions = await kv.list(KV.sessions);
15847
15924
  sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
15925
+ const limit = parsePositiveLimit(req.query_params?.["limit"]);
15848
15926
  return {
15849
15927
  status_code: 200,
15850
15928
  body: {
15851
15929
  success: true,
15852
- sessions
15930
+ sessions: limit !== void 0 ? sessions.slice(0, limit) : sessions
15853
15931
  }
15854
15932
  };
15855
15933
  });
@@ -16121,10 +16199,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
16121
16199
  const wildcardAgent = normalizedAgentId === "*";
16122
16200
  const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
16123
16201
  const filtered = filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions;
16124
- const summaries = await Promise.all(filtered.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
16202
+ const limit = parsePositiveLimit(req.query_params?.["limit"]);
16203
+ const sliced = limit !== void 0 ? filtered.slice(0, limit) : filtered;
16204
+ const summaries = await Promise.all(sliced.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
16125
16205
  return {
16126
16206
  status_code: 200,
16127
- body: { sessions: filtered.map((s, i) => summaries[i] ? {
16207
+ body: { sessions: sliced.map((s, i) => summaries[i] ? {
16128
16208
  ...s,
16129
16209
  summary: summaries[i]
16130
16210
  } : s) }
@@ -16321,13 +16401,22 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
16321
16401
  sdk.registerFunction("api::consolidate", async (req) => {
16322
16402
  const authErr = checkAuth(req, secret);
16323
16403
  if (authErr) return authErr;
16324
- return {
16325
- status_code: 200,
16326
- body: await sdk.trigger({
16327
- function_id: "mem::consolidate",
16328
- payload: req.body
16329
- })
16330
- };
16404
+ try {
16405
+ return {
16406
+ status_code: 200,
16407
+ body: await sdk.trigger({
16408
+ function_id: "mem::consolidate",
16409
+ payload: req.body
16410
+ })
16411
+ };
16412
+ } catch (err) {
16413
+ const message = err instanceof Error ? err.message : String(err);
16414
+ logger.warn("api::consolidate failed", { error: message });
16415
+ return {
16416
+ status_code: 500,
16417
+ body: { error: message }
16418
+ };
16419
+ }
16331
16420
  });
16332
16421
  sdk.registerTrigger({
16333
16422
  type: "http",
@@ -22171,6 +22260,15 @@ async function main() {
22171
22260
  if (snapshotConfig.enabled) {
22172
22261
  registerSnapshotFunction(sdk, kv, snapshotConfig.dir);
22173
22262
  bootLog(`Git snapshots: ${snapshotConfig.dir} (every ${snapshotConfig.interval}s)`);
22263
+ const snapshotIntervalMs = snapshotConfig.interval * 1e3;
22264
+ setInterval(async () => {
22265
+ try {
22266
+ await sdk.trigger({
22267
+ function_id: "mem::snapshot-create",
22268
+ payload: {}
22269
+ });
22270
+ } catch {}
22271
+ }, snapshotIntervalMs).unref();
22174
22272
  }
22175
22273
  const bm25Index = getSearchIndex();
22176
22274
  const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");