ziiagentmemory 0.1.0 → 0.2.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-Dl4Re_-n.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-Dl4Re_-n.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-Dl4Re_-n.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-Dl4Re_-n.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
@@ -1301,6 +1301,109 @@ var StateKV = class {
1301
1301
  }
1302
1302
  };
1303
1303
  //#endregion
1304
+ //#region src/state/cjk-segmenter.ts
1305
+ const cjkRequire = createRequire(import.meta.url);
1306
+ const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
1307
+ const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u;
1308
+ const HANGUL_RE = /\p{Script=Hangul}/u;
1309
+ const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
1310
+ const HANGUL_BLOCK_RE = /[가-힯]+/g;
1311
+ const hintShown = /* @__PURE__ */ new Set();
1312
+ function hasCjk(text) {
1313
+ return CJK_RE.test(text);
1314
+ }
1315
+ function showHintOnce(key, message) {
1316
+ if (hintShown.has(key)) return;
1317
+ hintShown.add(key);
1318
+ if (typeof process !== "undefined" && process.stderr?.write) process.stderr.write(`agentmemory: ${message}\n`);
1319
+ }
1320
+ let jiebaInstance = null;
1321
+ let jiebaLoaded = false;
1322
+ function getJieba() {
1323
+ if (jiebaLoaded) return jiebaInstance;
1324
+ jiebaLoaded = true;
1325
+ try {
1326
+ const mod = cjkRequire("@node-rs/jieba");
1327
+ try {
1328
+ const dictMod = cjkRequire("@node-rs/jieba/dict");
1329
+ jiebaInstance = mod.Jieba.withDict(dictMod.dict);
1330
+ } catch {
1331
+ jiebaInstance = new mod.Jieba();
1332
+ }
1333
+ return jiebaInstance;
1334
+ } catch {
1335
+ showHintOnce("jieba", "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization");
1336
+ return null;
1337
+ }
1338
+ }
1339
+ let jaSegmenterInstance = null;
1340
+ let jaSegmenterLoaded = false;
1341
+ function getJaSegmenter() {
1342
+ if (jaSegmenterLoaded) return jaSegmenterInstance;
1343
+ jaSegmenterLoaded = true;
1344
+ try {
1345
+ jaSegmenterInstance = new (cjkRequire("tiny-segmenter"))();
1346
+ return jaSegmenterInstance;
1347
+ } catch {
1348
+ showHintOnce("tiny-segmenter", "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization");
1349
+ return null;
1350
+ }
1351
+ }
1352
+ function cleanTokens(tokens) {
1353
+ const out = [];
1354
+ for (const t of tokens) {
1355
+ const trimmed = t.trim();
1356
+ if (trimmed) out.push(trimmed);
1357
+ }
1358
+ return out;
1359
+ }
1360
+ function segmentHan(text) {
1361
+ const j = getJieba();
1362
+ if (!j) return [text];
1363
+ try {
1364
+ return cleanTokens(j.cut(text, true));
1365
+ } catch {
1366
+ return [text];
1367
+ }
1368
+ }
1369
+ function segmentKana(text) {
1370
+ const s = getJaSegmenter();
1371
+ if (!s) return [text];
1372
+ try {
1373
+ return cleanTokens(s.segment(text));
1374
+ } catch {
1375
+ return [text];
1376
+ }
1377
+ }
1378
+ function segmentHangul(text) {
1379
+ const out = [];
1380
+ for (const m of text.matchAll(HANGUL_BLOCK_RE)) if (m[0]) out.push(m[0]);
1381
+ return out;
1382
+ }
1383
+ function segmentCjk(text) {
1384
+ if (!hasCjk(text)) return [text];
1385
+ const out = [];
1386
+ let cursor = 0;
1387
+ for (const m of text.matchAll(CJK_RUN_RE)) {
1388
+ const start = m.index ?? 0;
1389
+ const run = m[0];
1390
+ const end = start + run.length;
1391
+ if (start > cursor) {
1392
+ const piece = text.slice(cursor, start).trim();
1393
+ if (piece) out.push(piece);
1394
+ }
1395
+ if (HANGUL_RE.test(run)) out.push(...segmentHangul(run));
1396
+ else if (KANA_RE.test(run)) out.push(...segmentKana(run));
1397
+ else out.push(...segmentHan(run));
1398
+ cursor = end;
1399
+ }
1400
+ if (cursor < text.length) {
1401
+ const trailing = text.slice(cursor).trim();
1402
+ if (trailing) out.push(trailing);
1403
+ }
1404
+ return out;
1405
+ }
1406
+ //#endregion
1304
1407
  //#region src/state/schema.ts
1305
1408
  const KV = {
1306
1409
  sessions: "mem:sessions",
@@ -1365,9 +1468,33 @@ function generateId(prefix) {
1365
1468
  function fingerprintId(prefix, content) {
1366
1469
  return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
1367
1470
  }
1471
+ /**
1472
+ * CJK-aware tokenizer for Jaccard similarity.
1473
+ *
1474
+ * For English/whitespace-delimited text we keep the original behaviour:
1475
+ * split on whitespace, filter tokens longer than 2 characters.
1476
+ *
1477
+ * For CJK text (Chinese/Japanese/Korean) whitespace splitting produces
1478
+ * near-disjoint sets because CJK text is typically written without spaces.
1479
+ * We reuse the existing `segmentCjk` function and additionally emit
1480
+ * character bigrams from CJK runs so that no-space Chinese text still
1481
+ * produces useful overlap.
1482
+ */
1483
+ function tokenizeForJaccard(text) {
1484
+ if (!hasCjk(text)) return new Set(text.split(/\s+/).filter((t) => t.length > 2));
1485
+ const segments = segmentCjk(text);
1486
+ const tokens = /* @__PURE__ */ new Set();
1487
+ for (const seg of segments) {
1488
+ const cleaned = seg.replace(/[^\p{L}\p{N}]/gu, "").toLowerCase();
1489
+ if (cleaned.length > 2) tokens.add(cleaned);
1490
+ const cjkChars = seg.match(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu);
1491
+ if (cjkChars && cjkChars.length >= 2) for (let i = 0; i < cjkChars.length - 1; i++) tokens.add(cjkChars[i] + cjkChars[i + 1]);
1492
+ }
1493
+ return tokens;
1494
+ }
1368
1495
  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));
1496
+ const setA = tokenizeForJaccard(a);
1497
+ const setB = tokenizeForJaccard(b);
1371
1498
  if (setA.size === 0 && setB.size === 0) return 1;
1372
1499
  if (setA.size === 0 || setB.size === 0) return 0;
1373
1500
  let intersection = 0;
@@ -2429,109 +2556,6 @@ function getSynonyms(stemmedTerm) {
2429
2556
  return syns ? [...syns] : [];
2430
2557
  }
2431
2558
  //#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
2559
  //#region src/state/search-index.ts
2536
2560
  var SearchIndex = class SearchIndex {
2537
2561
  entries = /* @__PURE__ */ new Map();
@@ -5382,7 +5406,7 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
5382
5406
  error: "no_observations"
5383
5407
  };
5384
5408
  }
5385
- if (provider.name === "noop") {
5409
+ if (provider.name === "noop" || provider.name === "resilient(noop)") {
5386
5410
  logger.info("Summarize skipped — no LLM provider configured", { sessionId });
5387
5411
  return {
5388
5412
  success: false,
@@ -5993,6 +6017,34 @@ function registerPatternsFunction(sdk, kv) {
5993
6017
  });
5994
6018
  }
5995
6019
  //#endregion
6020
+ //#region src/state/memory-dedup.ts
6021
+ /**
6022
+ * Negation guard for memory dedup.
6023
+ *
6024
+ * Prevents "do X" from superseding "do not X" (and vice versa) even when
6025
+ * the two strings share enough tokens to exceed the Jaccard threshold.
6026
+ *
6027
+ * The guard is deliberately narrow and conservative:
6028
+ * - Only triggers when one side has a negation marker and the other does not.
6029
+ * - Supports English ("not", "never", "don't", "do not") and CJK
6030
+ * ("不要", "别", "無", "なし", "않") markers.
6031
+ * - If BOTH sides have negation markers, they are treated as compatible
6032
+ * (both say "don't do X") and the guard does not fire.
6033
+ */
6034
+ const EN_NEGATION = /\b(not|never|don't|do not|cannot|should not|must not|no)\b/i;
6035
+ const CJK_NEGATION = /不要|别|勿|無|无|不|なし|않|안|못/i;
6036
+ function hasNegation(text) {
6037
+ return EN_NEGATION.test(text) || CJK_NEGATION.test(text);
6038
+ }
6039
+ /**
6040
+ * Returns true if the two texts appear to be in a negation conflict
6041
+ * (one says do X, the other says do NOT do X) and should NOT be superseded.
6042
+ */
6043
+ function isNegationConflict(a, b) {
6044
+ if (hasNegation(a) === hasNegation(b)) return false;
6045
+ return true;
6046
+ }
6047
+ //#endregion
5996
6048
  //#region src/functions/remember.ts
5997
6049
  function registerRememberFunction(sdk, kv) {
5998
6050
  sdk.registerFunction("mem::remember", async (data) => {
@@ -6031,7 +6083,7 @@ function registerRememberFunction(sdk, kv) {
6031
6083
  for (const existing of existingMemories) {
6032
6084
  if (existing.isLatest === false) continue;
6033
6085
  if (project && existing.project && existing.project !== project) continue;
6034
- if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
6086
+ if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7 && !isNegationConflict(lowerContent, existing.content.toLowerCase())) {
6035
6087
  supersededId = existing.id;
6036
6088
  supersededVersion = existing.version ?? 1;
6037
6089
  supersededMemory = existing;
@@ -15457,6 +15509,10 @@ function parseOptionalInt(raw) {
15457
15509
  const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
15458
15510
  return Number.isFinite(n) ? n : void 0;
15459
15511
  }
15512
+ function parsePositiveLimit(raw) {
15513
+ const n = parseOptionalInt(raw);
15514
+ return n !== void 0 && n > 0 ? n : void 0;
15515
+ }
15460
15516
  function checkAuth(req, secret) {
15461
15517
  if (!secret) return null;
15462
15518
  const auth = req.headers?.["authorization"] || req.headers?.["Authorization"];
@@ -15845,11 +15901,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
15845
15901
  if (authErr) return authErr;
15846
15902
  const sessions = await kv.list(KV.sessions);
15847
15903
  sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
15904
+ const limit = parsePositiveLimit(req.query_params?.["limit"]);
15848
15905
  return {
15849
15906
  status_code: 200,
15850
15907
  body: {
15851
15908
  success: true,
15852
- sessions
15909
+ sessions: limit !== void 0 ? sessions.slice(0, limit) : sessions
15853
15910
  }
15854
15911
  };
15855
15912
  });
@@ -16121,10 +16178,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
16121
16178
  const wildcardAgent = normalizedAgentId === "*";
16122
16179
  const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
16123
16180
  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)));
16181
+ const limit = parsePositiveLimit(req.query_params?.["limit"]);
16182
+ const sliced = limit !== void 0 ? filtered.slice(0, limit) : filtered;
16183
+ const summaries = await Promise.all(sliced.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
16125
16184
  return {
16126
16185
  status_code: 200,
16127
- body: { sessions: filtered.map((s, i) => summaries[i] ? {
16186
+ body: { sessions: sliced.map((s, i) => summaries[i] ? {
16128
16187
  ...s,
16129
16188
  summary: summaries[i]
16130
16189
  } : s) }
@@ -22171,6 +22230,15 @@ async function main() {
22171
22230
  if (snapshotConfig.enabled) {
22172
22231
  registerSnapshotFunction(sdk, kv, snapshotConfig.dir);
22173
22232
  bootLog(`Git snapshots: ${snapshotConfig.dir} (every ${snapshotConfig.interval}s)`);
22233
+ const snapshotIntervalMs = snapshotConfig.interval * 1e3;
22234
+ setInterval(async () => {
22235
+ try {
22236
+ await sdk.trigger({
22237
+ function_id: "mem::snapshot-create",
22238
+ payload: {}
22239
+ });
22240
+ } catch {}
22241
+ }, snapshotIntervalMs).unref();
22174
22242
  }
22175
22243
  const bm25Index = getSearchIndex();
22176
22244
  const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");