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 +6 -6
- package/dist/{image-refs-C2YU7BIm.mjs → image-refs-Cc7-ua5j.mjs} +2 -2
- package/dist/{image-refs-C2YU7BIm.mjs.map → image-refs-Cc7-ua5j.mjs.map} +1 -1
- package/dist/index.mjs +224 -126
- package/dist/index.mjs.map +1 -1
- package/dist/schema-BQLY6d8C.mjs +206 -0
- package/dist/schema-BQLY6d8C.mjs.map +1 -0
- package/dist/{src-Cr3pH_uH.mjs → src-Dl5_tJgD.mjs} +104 -134
- package/dist/src-Dl5_tJgD.mjs.map +1 -0
- package/dist/{standalone-C6KzNkt5.mjs → standalone-CSnfnb1m.mjs} +2 -2
- package/dist/{standalone-C6KzNkt5.mjs.map → standalone-CSnfnb1m.mjs.map} +1 -1
- package/dist/standalone.mjs +2 -0
- package/dist/standalone.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/schema-CNiCWzV9.mjs +0 -78
- package/dist/schema-CNiCWzV9.mjs.map +0 -1
- package/dist/src-Cr3pH_uH.mjs.map +0 -1
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
34
|
+
//# sourceMappingURL=image-refs-Cc7-ua5j.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-refs-
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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") ||
|
|
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 =
|
|
1370
|
-
const setB =
|
|
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
|
|
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:
|
|
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
|
-
|
|
16325
|
-
|
|
16326
|
-
|
|
16327
|
-
|
|
16328
|
-
|
|
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");
|