@agentmemory/agentmemory 0.9.27 → 0.9.29
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/.env.example +15 -6
- package/AGENTS.md +7 -7
- package/README.md +292 -164
- package/dist/.env.example +15 -6
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +427 -177
- package/dist/cli.mjs.map +1 -1
- package/dist/{connect-bmZ5eqYN.mjs → connect-Qaeq15TQ.mjs} +717 -65
- package/dist/connect-Qaeq15TQ.mjs.map +1 -0
- package/dist/docker-compose.yml +2 -2
- package/dist/hooks/antigravity-bridge.d.mts +8 -0
- package/dist/hooks/antigravity-bridge.d.mts.map +1 -0
- package/dist/hooks/antigravity-bridge.mjs +114 -0
- package/dist/hooks/antigravity-bridge.mjs.map +1 -0
- package/dist/hooks/notification.mjs +21 -5
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-commit.mjs +15 -2
- package/dist/hooks/post-commit.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +16 -4
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +16 -4
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +14 -3
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +3 -2
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +16 -4
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +84 -19
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +24 -5
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +3 -8
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +16 -4
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +16 -4
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +16 -4
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/{image-refs-B5Zf_Djo.mjs → image-refs-BKLmp_sD.mjs} +4 -22
- package/dist/image-refs-BKLmp_sD.mjs.map +1 -0
- package/dist/image-refs-CJgZmb9X.mjs +34 -0
- package/dist/image-refs-CJgZmb9X.mjs.map +1 -0
- package/dist/index.mjs +1681 -911
- package/dist/index.mjs.map +1 -1
- package/dist/{image-store-Gpo2mgM9.mjs → keyed-mutex-DTHtcZrt.mjs} +14 -11
- package/dist/keyed-mutex-DTHtcZrt.mjs.map +1 -0
- package/dist/{logger-yHTcEBAI.mjs → logger-BEB7pCI9.mjs} +1 -1
- package/dist/{logger-yHTcEBAI.mjs.map → logger-BEB7pCI9.mjs.map} +1 -1
- package/dist/schema-BFZhNTF7.mjs +197 -0
- package/dist/schema-BFZhNTF7.mjs.map +1 -0
- package/dist/{src-MOg2zKJs.mjs → src-G7yt8gGm.mjs} +1521 -854
- package/dist/src-G7yt8gGm.mjs.map +1 -0
- package/dist/{standalone-BORXC71-.mjs → standalone-BqNJDJ04.mjs} +30 -16
- package/dist/standalone-BqNJDJ04.mjs.map +1 -0
- package/dist/standalone.d.mts.map +1 -1
- package/dist/standalone.mjs +55 -15
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-D8XVVPbJ.mjs → tools-registry-BV_2bu7z.mjs} +55 -17
- package/dist/tools-registry-BV_2bu7z.mjs.map +1 -0
- package/dist/viewer/favicon.svg +35 -1
- package/dist/viewer/index.html +473 -83
- package/docker-compose.yml +2 -2
- package/integrations/pi/README.md +77 -0
- package/integrations/pi/index.ts +403 -0
- package/integrations/pi/package.json +32 -0
- package/integrations/pi/security.ts +35 -0
- package/package.json +6 -4
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/.devin-plugin/plugin.json +32 -0
- package/plugin/cursor/hooks.json +41 -0
- package/plugin/cursor/mcp.json +12 -0
- package/plugin/hooks/hooks.antigravity.json +43 -0
- package/plugin/hooks/hooks.devin.json +67 -0
- package/plugin/hooks/hooks.droid.json +55 -0
- package/plugin/opencode/README.md +1 -1
- package/plugin/opencode/agentmemory-capture.ts +73 -13
- package/plugin/opencode/plugin.json +1 -1
- package/plugin/plugin.json +2 -2
- package/plugin/scripts/antigravity-bridge.d.mts +8 -0
- package/plugin/scripts/antigravity-bridge.d.mts.map +1 -0
- package/plugin/scripts/antigravity-bridge.mjs +114 -0
- package/plugin/scripts/antigravity-bridge.mjs.map +1 -0
- package/plugin/scripts/notification.mjs +21 -5
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-commit.mjs +15 -2
- package/plugin/scripts/post-commit.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +16 -4
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +16 -4
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +14 -3
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +3 -2
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +16 -4
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +84 -19
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +24 -5
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +3 -8
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +16 -4
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +16 -4
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +16 -4
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/plugin/skills/_shared/TROUBLESHOOTING.md +38 -0
- package/plugin/skills/agentmemory-agents/REFERENCE.md +31 -0
- package/plugin/skills/agentmemory-agents/SKILL.md +34 -0
- package/plugin/skills/agentmemory-architecture/SKILL.md +33 -0
- package/plugin/skills/agentmemory-config/REFERENCE.md +45 -0
- package/plugin/skills/agentmemory-config/SKILL.md +37 -0
- package/plugin/skills/agentmemory-hooks/REFERENCE.md +20 -0
- package/plugin/skills/agentmemory-hooks/SKILL.md +39 -0
- package/plugin/skills/agentmemory-mcp-tools/REFERENCE.md +66 -0
- package/plugin/skills/agentmemory-mcp-tools/SKILL.md +39 -0
- package/plugin/skills/agentmemory-rest-api/REFERENCE.md +142 -0
- package/plugin/skills/agentmemory-rest-api/SKILL.md +43 -0
- package/plugin/skills/commit-context/EXAMPLES.md +71 -0
- package/plugin/skills/commit-context/SKILL.md +53 -8
- package/plugin/skills/commit-history/EXAMPLES.md +71 -0
- package/plugin/skills/commit-history/SKILL.md +51 -9
- package/plugin/skills/forget/EXAMPLES.md +75 -0
- package/plugin/skills/forget/SKILL.md +57 -14
- package/plugin/skills/handoff/EXAMPLES.md +56 -0
- package/plugin/skills/handoff/SKILL.md +56 -9
- package/plugin/skills/lesson/SKILL.md +64 -0
- package/plugin/skills/memory-discipline/SKILL.md +57 -0
- package/plugin/skills/recall/EXAMPLES.md +74 -0
- package/plugin/skills/recall/SKILL.md +53 -11
- package/plugin/skills/recap/EXAMPLES.md +60 -0
- package/plugin/skills/recap/SKILL.md +51 -13
- package/plugin/skills/remember/EXAMPLES.md +67 -0
- package/plugin/skills/remember/SKILL.md +58 -17
- package/plugin/skills/session-history/EXAMPLES.md +59 -0
- package/plugin/skills/session-history/SKILL.md +54 -11
- package/plugin/skills/write-agentmemory-skill/SKILL.md +40 -0
- package/dist/connect-bmZ5eqYN.mjs.map +0 -1
- package/dist/image-refs-B5Zf_Djo.mjs.map +0 -1
- package/dist/image-store-Gpo2mgM9.mjs.map +0 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
- package/dist/schema-CNiCWzV9.mjs +0 -78
- package/dist/schema-CNiCWzV9.mjs.map +0 -1
- package/dist/src-MOg2zKJs.mjs.map +0 -1
- package/dist/standalone-BORXC71-.mjs.map +0 -1
- package/dist/tools-registry-D8XVVPbJ.mjs.map +0 -1
- package/dist/version-9DNrRyph.mjs +0 -6
- package/dist/version-9DNrRyph.mjs.map +0 -1
- package/plugin/scripts/diagnostics.d.mts +0 -17
- package/plugin/scripts/diagnostics.d.mts.map +0 -1
- package/plugin/scripts/diagnostics.mjs.map +0 -1
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
|
|
2
1
|
import { existsSync } from "node:fs";
|
|
3
2
|
import { join, resolve, sep } from "node:path";
|
|
4
3
|
import { homedir } from "node:os";
|
|
5
4
|
import { createHash } from "node:crypto";
|
|
6
5
|
import { mkdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
7
6
|
//#region src/utils/image-store.ts
|
|
8
|
-
var image_store_exports = /* @__PURE__ */ __exportAll({
|
|
9
|
-
IMAGES_DIR: () => IMAGES_DIR,
|
|
10
|
-
deleteImage: () => deleteImage,
|
|
11
|
-
getMaxBytes: () => getMaxBytes,
|
|
12
|
-
isManagedImagePath: () => isManagedImagePath,
|
|
13
|
-
saveImageToDisk: () => saveImageToDisk,
|
|
14
|
-
touchImage: () => touchImage
|
|
15
|
-
});
|
|
16
7
|
const IMAGES_DIR = join(homedir(), ".agentmemory", "images");
|
|
17
8
|
const DEFAULT_MAX_BYTES = 500 * 1024 * 1024;
|
|
18
9
|
function getMaxBytes() {
|
|
@@ -80,6 +71,18 @@ async function touchImage(filePath) {
|
|
|
80
71
|
} catch (err) {}
|
|
81
72
|
}
|
|
82
73
|
//#endregion
|
|
83
|
-
|
|
74
|
+
//#region src/state/keyed-mutex.ts
|
|
75
|
+
const locks = /* @__PURE__ */ new Map();
|
|
76
|
+
function withKeyedLock(key, fn) {
|
|
77
|
+
const next = (locks.get(key) ?? Promise.resolve()).then(fn, fn);
|
|
78
|
+
const cleanup = next.then(() => {}, () => {});
|
|
79
|
+
locks.set(key, cleanup);
|
|
80
|
+
cleanup.then(() => {
|
|
81
|
+
if (locks.get(key) === cleanup) locks.delete(key);
|
|
82
|
+
});
|
|
83
|
+
return next;
|
|
84
|
+
}
|
|
85
|
+
//#endregion
|
|
86
|
+
export { isManagedImagePath as a, getMaxBytes as i, IMAGES_DIR as n, saveImageToDisk as o, deleteImage as r, touchImage as s, withKeyedLock as t };
|
|
84
87
|
|
|
85
|
-
//# sourceMappingURL=
|
|
88
|
+
//# sourceMappingURL=keyed-mutex-DTHtcZrt.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keyed-mutex-DTHtcZrt.mjs","names":[],"sources":["../src/utils/image-store.ts","../src/state/keyed-mutex.ts"],"sourcesContent":["import { homedir } from \"node:os\";\nimport { join, resolve, sep } from \"node:path\";\nimport { existsSync } from \"node:fs\";\nimport { mkdir, writeFile, unlink, utimes, stat } from \"node:fs/promises\";\nimport { createHash } from \"node:crypto\";\n\nexport const IMAGES_DIR = join(homedir(), \".agentmemory\", \"images\");\n\nconst DEFAULT_MAX_BYTES = 500 * 1024 * 1024;\n\nexport function getMaxBytes(): number {\n return Number(process.env.AGENTMEMORY_IMAGE_STORE_MAX_BYTES) || DEFAULT_MAX_BYTES;\n}\n\nexport function isManagedImagePath(filePath: string): boolean {\n const resolved = resolve(filePath);\n const normalizedImagesDir = resolve(IMAGES_DIR);\n return resolved.startsWith(normalizedImagesDir + sep) || resolved === normalizedImagesDir;\n}\n\nfunction contentHash(data: string): string {\n return createHash(\"sha256\").update(data).digest(\"hex\");\n}\n\nexport async function saveImageToDisk(base64Data: string): Promise<{ filePath: string; bytesWritten: number }> {\n if (!base64Data) return { filePath: \"\", bytesWritten: 0 };\n\n if (!existsSync(IMAGES_DIR)) {\n await mkdir(IMAGES_DIR, { recursive: true });\n }\n\n let cleanBase64 = base64Data;\n let ext = \"png\";\n\n if (base64Data.startsWith(\"data:image/\")) {\n const commaIdx = base64Data.indexOf(\",\");\n if (commaIdx !== -1) {\n const meta = base64Data.substring(0, commaIdx);\n if (meta.includes(\"jpeg\") || meta.includes(\"jpg\")) ext = \"jpg\";\n else if (meta.includes(\"webp\")) ext = \"webp\";\n else if (meta.includes(\"gif\")) ext = \"gif\";\n cleanBase64 = base64Data.substring(commaIdx + 1);\n }\n } else if (base64Data.startsWith(\"/9j/\")) {\n ext = \"jpg\";\n }\n\n const hash = contentHash(cleanBase64);\n const filePath = join(IMAGES_DIR, `${hash}.${ext}`);\n\n if (existsSync(filePath)) {\n return { filePath, bytesWritten: 0 };\n }\n\n const buffer = Buffer.from(cleanBase64, \"base64\");\n await writeFile(filePath, buffer);\n\n const s = await stat(filePath);\n\n return { filePath, bytesWritten: s.size };\n}\n\nexport async function deleteImage(filePath: string | undefined): Promise<{ deletedBytes: number }> {\n if (!filePath) return { deletedBytes: 0 };\n if (!isManagedImagePath(filePath)) return { deletedBytes: 0 };\n try {\n if (existsSync(filePath)) {\n const s = await stat(filePath);\n const size = s.size;\n await unlink(filePath);\n return { deletedBytes: size };\n }\n } catch (err) {\n console.error(\"[agentmemory] Failed to delete image context:\", err);\n }\n return { deletedBytes: 0 };\n}\n\n/** Touch an image file to update its mtime (marking it as recently used for LRU eviction) */\nexport async function touchImage(filePath: string): Promise<void> {\n if (!filePath || !isManagedImagePath(filePath)) return;\n try {\n if (existsSync(filePath)) {\n const now = new Date();\n await utimes(filePath, now, now);\n }\n } catch (err) {\n // Ignore touch errors silently\n }\n}\n","const locks = new Map<string, Promise<void>>();\n\nexport function withKeyedLock<T>(\n key: string,\n fn: () => Promise<T>,\n): Promise<T> {\n const prev = locks.get(key) ?? Promise.resolve();\n const next = prev.then(fn, fn);\n const cleanup = next.then(\n () => {},\n () => {},\n );\n locks.set(key, cleanup);\n cleanup.then(() => {\n if (locks.get(key) === cleanup) locks.delete(key);\n });\n return next;\n}\n"],"mappings":";;;;;;AAMA,MAAa,aAAa,KAAK,SAAS,EAAE,gBAAgB,SAAS;AAEnE,MAAM,oBAAoB,MAAM,OAAO;AAEvC,SAAgB,cAAsB;AACpC,QAAO,OAAO,QAAQ,IAAI,kCAAkC,IAAI;;AAGlE,SAAgB,mBAAmB,UAA2B;CAC5D,MAAM,WAAW,QAAQ,SAAS;CAClC,MAAM,sBAAsB,QAAQ,WAAW;AAC/C,QAAO,SAAS,WAAW,sBAAsB,IAAI,IAAI,aAAa;;AAGxE,SAAS,YAAY,MAAsB;AACzC,QAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;;AAGxD,eAAsB,gBAAgB,YAAyE;AAC7G,KAAI,CAAC,WAAY,QAAO;EAAE,UAAU;EAAI,cAAc;EAAG;AAEzD,KAAI,CAAC,WAAW,WAAW,CACzB,OAAM,MAAM,YAAY,EAAE,WAAW,MAAM,CAAC;CAG9C,IAAI,cAAc;CAClB,IAAI,MAAM;AAEV,KAAI,WAAW,WAAW,cAAc,EAAE;EACvC,MAAM,WAAW,WAAW,QAAQ,IAAI;AACxC,MAAI,aAAa,IAAI;GACnB,MAAM,OAAO,WAAW,UAAU,GAAG,SAAS;AAC9C,OAAI,KAAK,SAAS,OAAO,IAAI,KAAK,SAAS,MAAM,CAAE,OAAM;YAChD,KAAK,SAAS,OAAO,CAAE,OAAM;YAC7B,KAAK,SAAS,MAAM,CAAE,OAAM;AACrC,iBAAc,WAAW,UAAU,WAAW,EAAE;;YAE1C,WAAW,WAAW,OAAO,CACrC,OAAM;CAIT,MAAM,WAAW,KAAK,YAAY,GADrB,YAAY,YACgB,CAAC,GAAG,MAAM;AAEnD,KAAI,WAAW,SAAS,CACtB,QAAO;EAAE;EAAU,cAAc;EAAG;AAItC,OAAM,UAAU,UADD,OAAO,KAAK,aAAa,SACR,CAAC;AAIjC,QAAO;EAAE;EAAU,eAAc,MAFjB,KAAK,SAAS,EAEK;EAAM;;AAG3C,eAAsB,YAAY,UAAiE;AACjG,KAAI,CAAC,SAAU,QAAO,EAAE,cAAc,GAAG;AACzC,KAAI,CAAC,mBAAmB,SAAS,CAAE,QAAO,EAAE,cAAc,GAAG;AAC7D,KAAI;AACF,MAAI,WAAW,SAAS,EAAE;GAExB,MAAM,QAAO,MADG,KAAK,SAAS,EACf;AACf,SAAM,OAAO,SAAS;AACtB,UAAO,EAAE,cAAc,MAAM;;UAExB,KAAK;AACZ,UAAQ,MAAM,iDAAiD,IAAI;;AAErE,QAAO,EAAE,cAAc,GAAG;;;AAI5B,eAAsB,WAAW,UAAiC;AAChE,KAAI,CAAC,YAAY,CAAC,mBAAmB,SAAS,CAAE;AAChD,KAAI;AACF,MAAI,WAAW,SAAS,EAAE;GACxB,MAAM,sBAAM,IAAI,MAAM;AACtB,SAAM,OAAO,UAAU,KAAK,IAAI;;UAE3B,KAAK;;;;ACtFhB,MAAM,wBAAQ,IAAI,KAA4B;AAE9C,SAAgB,cACd,KACA,IACY;CAEZ,MAAM,QADO,MAAM,IAAI,IAAI,IAAI,QAAQ,SAAS,EAC9B,KAAK,IAAI,GAAG;CAC9B,MAAM,UAAU,KAAK,WACb,UACA,GACP;AACD,OAAM,IAAI,KAAK,QAAQ;AACvB,SAAQ,WAAW;AACjB,MAAI,MAAM,IAAI,IAAI,KAAK,QAAS,OAAM,OAAO,IAAI;GACjD;AACF,QAAO"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger-
|
|
1
|
+
{"version":3,"file":"logger-BEB7pCI9.mjs","names":[],"sources":["../src/logger.ts"],"sourcesContent":["// Thin logging shim for agentmemory.\n//\n// iii-sdk v0.11 dropped `getContext()`, which had been the source of a\n// contextual logger in every function handler (`getContext().logger`).\n// Migrating directly to the v0.11 OTEL-based `getLogger()` would force\n// every call site to care about the OTEL Logger API shape (`emit(...)`\n// with severity numbers and attributes maps). Instead, this module\n// exposes a single `logger` singleton with the same `.info/.warn/.error`\n// signature the old code used, so the mechanical replacement across\n// 30+ function files is: drop the `getContext` import, drop the\n// `const ctx = getContext();` line, and rename `ctx.logger.*` to\n// `logger.*`. Nothing else changes.\n//\n// Output goes to stderr as `[agentmemory] <level> <msg> <json-fields>`.\n// The iii-engine's `iii-exec` worker runs the agentmemory binary as a\n// child process and forwards stderr into `docker logs\n// agentmemory-iii-engine-1`, so these lines end up next to the engine's\n// own output without needing any OTEL wiring. If we later want\n// structured OTEL logs, this file is the only thing that changes.\n//\n// See rohitg00/agentmemory#143 follow-up — the #116 migration updated\n// test mocks but left the real `getContext()` imports in place, which\n// passed `npm test` (tests mock iii-sdk) and `npm run build` (tsdown\n// doesn't type-check) but crashed `node dist/index.mjs` on first\n// import.\n\ntype Fields = Record<string, unknown> | undefined;\n\nfunction fmt(level: string, msg: string, fields: Fields): string {\n if (!fields || Object.keys(fields).length === 0) {\n return `[agentmemory] ${level} ${msg}`;\n }\n try {\n return `[agentmemory] ${level} ${msg} ${JSON.stringify(fields)}`;\n } catch {\n // Fields contained a circular reference or a BigInt — fall back\n // to the plain message so a log line never throws.\n return `[agentmemory] ${level} ${msg}`;\n }\n}\n\nfunction emit(level: string, msg: string, fields: Fields): void {\n try {\n process.stderr.write(fmt(level, msg, fields) + \"\\n\");\n } catch {\n // stderr is unavailable in some weird test/worker contexts — swallow\n // so no log line can ever crash a handler.\n }\n}\n\nexport const logger = {\n info(msg: string, fields?: Fields): void {\n emit(\"info\", msg, fields);\n },\n warn(msg: string, fields?: Fields): void {\n emit(\"warn\", msg, fields);\n },\n error(msg: string, fields?: Fields): void {\n emit(\"error\", msg, fields);\n },\n};\n\n// ---------- boot log ----------\n//\n// `bootLog` is for the one-shot status lines that every register-*\n// function used to dump via `console.log` during engine startup. On a\n// fresh install that's ~25 lines of `[agentmemory] X enabled` noise\n// before the user can see a prompt. In quiet mode (default), each\n// line is captured into a buffer and discarded; the CLI surfaces a\n// single compressed summary instead. In verbose mode (set by\n// `--verbose` or `AGENTMEMORY_VERBOSE=1`) the lines pass straight\n// through to stderr exactly like the old console.log calls.\n\nlet bootVerbose =\n process.env[\"AGENTMEMORY_VERBOSE\"] === \"1\" ||\n process.env[\"AGENTMEMORY_VERBOSE\"] === \"true\";\n\nconst bootBuffer: string[] = [];\n\nexport function setBootVerbose(enabled: boolean): void {\n bootVerbose = enabled;\n}\n\nexport function isBootVerbose(): boolean {\n return bootVerbose;\n}\n\nexport function bootLog(msg: string): void {\n if (bootVerbose) {\n try {\n process.stderr.write(`[agentmemory] ${msg}\\n`);\n } catch {\n // stderr unavailable — drop.\n }\n return;\n }\n if (bootBuffer.length < 500) bootBuffer.push(msg);\n}\n\nexport function bootWarn(msg: string): void {\n // Warnings always surface; they're rare and the user needs to see\n // them even when the rest of the boot log is suppressed.\n try {\n process.stderr.write(`[agentmemory] warn ${msg}\\n`);\n } catch {}\n}\n\nexport function getBootBuffer(): readonly string[] {\n return bootBuffer;\n}\n"],"mappings":";AA4BA,SAAS,IAAI,OAAe,KAAa,QAAwB;AAC/D,KAAI,CAAC,UAAU,OAAO,KAAK,OAAO,CAAC,WAAW,EAC5C,QAAO,iBAAiB,MAAM,GAAG;AAEnC,KAAI;AACF,SAAO,iBAAiB,MAAM,GAAG,IAAI,GAAG,KAAK,UAAU,OAAO;SACxD;AAGN,SAAO,iBAAiB,MAAM,GAAG;;;AAIrC,SAAS,KAAK,OAAe,KAAa,QAAsB;AAC9D,KAAI;AACF,UAAQ,OAAO,MAAM,IAAI,OAAO,KAAK,OAAO,GAAG,KAAK;SAC9C;;AAMV,MAAa,SAAS;CACpB,KAAK,KAAa,QAAuB;AACvC,OAAK,QAAQ,KAAK,OAAO;;CAE3B,KAAK,KAAa,QAAuB;AACvC,OAAK,QAAQ,KAAK,OAAO;;CAE3B,MAAM,KAAa,QAAuB;AACxC,OAAK,SAAS,KAAK,OAAO;;CAE7B;AAaD,IAAI,cACF,QAAQ,IAAI,2BAA2B,OACvC,QAAQ,IAAI,2BAA2B;AAEzC,MAAM,aAAuB,EAAE;AAE/B,SAAgB,eAAe,SAAwB;AACrD,eAAc;;AAOhB,SAAgB,QAAQ,KAAmB;AACzC,KAAI,aAAa;AACf,MAAI;AACF,WAAQ,OAAO,MAAM,iBAAiB,IAAI,IAAI;UACxC;AAGR;;AAEF,KAAI,WAAW,SAAS,IAAK,YAAW,KAAK,IAAI"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
//#region src/state/cjk-segmenter.ts
|
|
4
|
+
const cjkRequire = createRequire(import.meta.url);
|
|
5
|
+
const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
6
|
+
const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
7
|
+
const HANGUL_RE = /\p{Script=Hangul}/u;
|
|
8
|
+
const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
|
|
9
|
+
const HANGUL_BLOCK_RE = /[가-]+/g;
|
|
10
|
+
const hintShown = /* @__PURE__ */ new Set();
|
|
11
|
+
function hasCjk(text) {
|
|
12
|
+
return CJK_RE.test(text);
|
|
13
|
+
}
|
|
14
|
+
function showHintOnce(key, message) {
|
|
15
|
+
if (hintShown.has(key)) return;
|
|
16
|
+
hintShown.add(key);
|
|
17
|
+
if (typeof process !== "undefined" && process.stderr?.write) process.stderr.write(`agentmemory: ${message}\n`);
|
|
18
|
+
}
|
|
19
|
+
let jiebaInstance = null;
|
|
20
|
+
let jiebaLoaded = false;
|
|
21
|
+
function getJieba() {
|
|
22
|
+
if (jiebaLoaded) return jiebaInstance;
|
|
23
|
+
jiebaLoaded = true;
|
|
24
|
+
try {
|
|
25
|
+
const mod = cjkRequire("@node-rs/jieba");
|
|
26
|
+
try {
|
|
27
|
+
const dictMod = cjkRequire("@node-rs/jieba/dict");
|
|
28
|
+
jiebaInstance = mod.Jieba.withDict(dictMod.dict);
|
|
29
|
+
} catch {
|
|
30
|
+
jiebaInstance = new mod.Jieba();
|
|
31
|
+
}
|
|
32
|
+
return jiebaInstance;
|
|
33
|
+
} catch {
|
|
34
|
+
showHintOnce("jieba", "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization");
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let jaSegmenterInstance = null;
|
|
39
|
+
let jaSegmenterLoaded = false;
|
|
40
|
+
function getJaSegmenter() {
|
|
41
|
+
if (jaSegmenterLoaded) return jaSegmenterInstance;
|
|
42
|
+
jaSegmenterLoaded = true;
|
|
43
|
+
try {
|
|
44
|
+
jaSegmenterInstance = new (cjkRequire("tiny-segmenter"))();
|
|
45
|
+
return jaSegmenterInstance;
|
|
46
|
+
} catch {
|
|
47
|
+
showHintOnce("tiny-segmenter", "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization");
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function cleanTokens(tokens) {
|
|
52
|
+
const out = [];
|
|
53
|
+
for (const t of tokens) {
|
|
54
|
+
const trimmed = t.trim();
|
|
55
|
+
if (trimmed) out.push(trimmed);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function segmentHan(text) {
|
|
60
|
+
const j = getJieba();
|
|
61
|
+
if (!j) return [text];
|
|
62
|
+
try {
|
|
63
|
+
return cleanTokens(j.cut(text, true));
|
|
64
|
+
} catch {
|
|
65
|
+
return [text];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function segmentKana(text) {
|
|
69
|
+
const s = getJaSegmenter();
|
|
70
|
+
if (!s) return [text];
|
|
71
|
+
try {
|
|
72
|
+
return cleanTokens(s.segment(text));
|
|
73
|
+
} catch {
|
|
74
|
+
return [text];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function segmentHangul(text) {
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const m of text.matchAll(HANGUL_BLOCK_RE)) if (m[0]) out.push(m[0]);
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function segmentCjk(text) {
|
|
83
|
+
if (!hasCjk(text)) return [text];
|
|
84
|
+
const out = [];
|
|
85
|
+
let cursor = 0;
|
|
86
|
+
for (const m of text.matchAll(CJK_RUN_RE)) {
|
|
87
|
+
const start = m.index ?? 0;
|
|
88
|
+
const run = m[0];
|
|
89
|
+
const end = start + run.length;
|
|
90
|
+
if (start > cursor) {
|
|
91
|
+
const piece = text.slice(cursor, start).trim();
|
|
92
|
+
if (piece) out.push(piece);
|
|
93
|
+
}
|
|
94
|
+
if (HANGUL_RE.test(run)) out.push(...segmentHangul(run));
|
|
95
|
+
else if (KANA_RE.test(run)) out.push(...segmentKana(run));
|
|
96
|
+
else out.push(...segmentHan(run));
|
|
97
|
+
cursor = end;
|
|
98
|
+
}
|
|
99
|
+
if (cursor < text.length) {
|
|
100
|
+
const trailing = text.slice(cursor).trim();
|
|
101
|
+
if (trailing) out.push(trailing);
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
//#region src/state/schema.ts
|
|
107
|
+
const KV = {
|
|
108
|
+
sessions: "mem:sessions",
|
|
109
|
+
observations: (sessionId) => `mem:obs:${sessionId}`,
|
|
110
|
+
memories: "mem:memories",
|
|
111
|
+
summaries: "mem:summaries",
|
|
112
|
+
config: "mem:config",
|
|
113
|
+
metrics: "mem:metrics",
|
|
114
|
+
health: "mem:health",
|
|
115
|
+
embeddings: (obsId) => `mem:emb:${obsId}`,
|
|
116
|
+
bm25Index: "mem:index:bm25",
|
|
117
|
+
relations: "mem:relations",
|
|
118
|
+
profiles: "mem:profiles",
|
|
119
|
+
claudeBridge: "mem:claude-bridge",
|
|
120
|
+
graphNodes: "mem:graph:nodes",
|
|
121
|
+
graphEdges: "mem:graph:edges",
|
|
122
|
+
graphSnapshot: "mem:graph:snapshot",
|
|
123
|
+
graphNameIndex: "mem:graph:name-index",
|
|
124
|
+
graphEdgeKey: "mem:graph:edge-key",
|
|
125
|
+
graphNodeDegree: "mem:graph:node-degree",
|
|
126
|
+
semantic: "mem:semantic",
|
|
127
|
+
procedural: "mem:procedural",
|
|
128
|
+
teamShared: (teamId) => `mem:team:${teamId}:shared`,
|
|
129
|
+
teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
|
|
130
|
+
teamProfile: (teamId) => `mem:team:${teamId}:profile`,
|
|
131
|
+
audit: "mem:audit",
|
|
132
|
+
actions: "mem:actions",
|
|
133
|
+
actionEdges: "mem:action-edges",
|
|
134
|
+
leases: "mem:leases",
|
|
135
|
+
routines: "mem:routines",
|
|
136
|
+
routineRuns: "mem:routine-runs",
|
|
137
|
+
signals: "mem:signals",
|
|
138
|
+
checkpoints: "mem:checkpoints",
|
|
139
|
+
mesh: "mem:mesh",
|
|
140
|
+
sketches: "mem:sketches",
|
|
141
|
+
facets: "mem:facets",
|
|
142
|
+
sentinels: "mem:sentinels",
|
|
143
|
+
crystals: "mem:crystals",
|
|
144
|
+
lessons: "mem:lessons",
|
|
145
|
+
insights: "mem:insights",
|
|
146
|
+
graphEdgeHistory: "mem:graph:edge-history",
|
|
147
|
+
enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
|
|
148
|
+
latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
|
|
149
|
+
retentionScores: "mem:retention",
|
|
150
|
+
accessLog: "mem:access",
|
|
151
|
+
imageRefs: "mem:image-refs",
|
|
152
|
+
imageEmbeddings: "mem:image-embeddings",
|
|
153
|
+
slots: "mem:slots",
|
|
154
|
+
globalSlots: "mem:slots:global",
|
|
155
|
+
state: "mem:state",
|
|
156
|
+
commits: "mem:commits",
|
|
157
|
+
recentSearches: "mem:recent-searches"
|
|
158
|
+
};
|
|
159
|
+
const STREAM = {
|
|
160
|
+
name: "mem-live",
|
|
161
|
+
group: (sessionId) => sessionId,
|
|
162
|
+
viewerGroup: "viewer"
|
|
163
|
+
};
|
|
164
|
+
function generateId(prefix) {
|
|
165
|
+
return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
166
|
+
}
|
|
167
|
+
function fingerprintId(prefix, content) {
|
|
168
|
+
return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
169
|
+
}
|
|
170
|
+
function jaccardTokens(text) {
|
|
171
|
+
if (!hasCjk(text)) return new Set(text.split(/\s+/).filter((t) => t.length > 2));
|
|
172
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const raw of text.split(/\s+/)) {
|
|
174
|
+
if (!raw) continue;
|
|
175
|
+
if (hasCjk(raw)) {
|
|
176
|
+
for (const seg of segmentCjk(raw)) if (seg) tokens.add(seg);
|
|
177
|
+
const chars = Array.from(raw);
|
|
178
|
+
if (chars.length === 1) tokens.add(chars[0]);
|
|
179
|
+
else for (let i = 0; i < chars.length - 1; i++) tokens.add(chars[i] + chars[i + 1]);
|
|
180
|
+
} else if (raw.length > 2) tokens.add(raw);
|
|
181
|
+
}
|
|
182
|
+
return tokens;
|
|
183
|
+
}
|
|
184
|
+
function jaccardSimilarity(a, b) {
|
|
185
|
+
const na = a.normalize("NFC");
|
|
186
|
+
const nb = b.normalize("NFC");
|
|
187
|
+
const setA = jaccardTokens(na);
|
|
188
|
+
const setB = jaccardTokens(nb);
|
|
189
|
+
if (setA.size === 0 || setB.size === 0) return na.trim().replace(/\s+/g, " ") === nb.trim().replace(/\s+/g, " ") ? 1 : 0;
|
|
190
|
+
let intersection = 0;
|
|
191
|
+
for (const word of setA) if (setB.has(word)) intersection++;
|
|
192
|
+
return intersection / (setA.size + setB.size - intersection);
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
195
|
+
export { jaccardSimilarity as a, generateId as i, STREAM as n, hasCjk as o, fingerprintId as r, segmentCjk as s, KV as t };
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=schema-BFZhNTF7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-BFZhNTF7.mjs","names":[],"sources":["../src/state/cjk-segmenter.ts","../src/state/schema.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nconst cjkRequire = createRequire(import.meta.url);\n\nconst CJK_RE = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u;\nconst HAN_RE = /\\p{Script=Han}/u;\nconst KANA_RE = /[\\p{Script=Hiragana}\\p{Script=Katakana}]/u;\nconst HANGUL_RE = /\\p{Script=Hangul}/u;\nconst CJK_RUN_RE = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+/gu;\nconst HANGUL_BLOCK_RE = /[가-]+/g;\n\ntype Script = \"han\" | \"kana\" | \"hangul\" | \"other\";\n\nconst hintShown = new Set<string>();\n\nexport function hasCjk(text: string): boolean {\n return CJK_RE.test(text);\n}\n\nexport function detectScript(text: string): Script {\n if (HAN_RE.test(text)) return \"han\";\n if (KANA_RE.test(text)) return \"kana\";\n if (HANGUL_RE.test(text)) return \"hangul\";\n return \"other\";\n}\n\nfunction showHintOnce(key: string, message: string): void {\n if (hintShown.has(key)) return;\n hintShown.add(key);\n if (typeof process !== \"undefined\" && process.stderr?.write) {\n process.stderr.write(`agentmemory: ${message}\\n`);\n }\n}\n\ninterface JiebaInstance {\n cut(text: string, hmm?: boolean): string[];\n}\n\nlet jiebaInstance: JiebaInstance | null = null;\nlet jiebaLoaded = false;\n\nfunction getJieba(): JiebaInstance | null {\n if (jiebaLoaded) return jiebaInstance;\n jiebaLoaded = true;\n try {\n const mod = cjkRequire(\"@node-rs/jieba\") as {\n Jieba: {\n new (): JiebaInstance;\n withDict(dict: Uint8Array): JiebaInstance;\n };\n };\n try {\n const dictMod = cjkRequire(\"@node-rs/jieba/dict\") as { dict: Uint8Array };\n jiebaInstance = mod.Jieba.withDict(dictMod.dict);\n } catch {\n jiebaInstance = new mod.Jieba();\n }\n return jiebaInstance;\n } catch {\n showHintOnce(\n \"jieba\",\n \"install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization\",\n );\n return null;\n }\n}\n\ninterface JaSegmenter {\n segment(text: string): string[];\n}\n\nlet jaSegmenterInstance: JaSegmenter | null = null;\nlet jaSegmenterLoaded = false;\n\nfunction getJaSegmenter(): JaSegmenter | null {\n if (jaSegmenterLoaded) return jaSegmenterInstance;\n jaSegmenterLoaded = true;\n try {\n const Ctor = cjkRequire(\"tiny-segmenter\") as new () => JaSegmenter;\n jaSegmenterInstance = new Ctor();\n return jaSegmenterInstance;\n } catch {\n showHintOnce(\n \"tiny-segmenter\",\n \"install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization\",\n );\n return null;\n }\n}\n\nfunction cleanTokens(tokens: string[]): string[] {\n const out: string[] = [];\n for (const t of tokens) {\n const trimmed = t.trim();\n if (trimmed) out.push(trimmed);\n }\n return out;\n}\n\nfunction segmentHan(text: string): string[] {\n const j = getJieba();\n if (!j) return [text];\n try {\n return cleanTokens(j.cut(text, true));\n } catch {\n return [text];\n }\n}\n\nfunction segmentKana(text: string): string[] {\n const s = getJaSegmenter();\n if (!s) return [text];\n try {\n return cleanTokens(s.segment(text));\n } catch {\n return [text];\n }\n}\n\nfunction segmentHangul(text: string): string[] {\n const out: string[] = [];\n for (const m of text.matchAll(HANGUL_BLOCK_RE)) {\n if (m[0]) out.push(m[0]);\n }\n return out;\n}\n\nexport function segmentCjk(text: string): string[] {\n if (!hasCjk(text)) return [text];\n\n const out: string[] = [];\n let cursor = 0;\n\n for (const m of text.matchAll(CJK_RUN_RE)) {\n const start = m.index ?? 0;\n const run = m[0];\n const end = start + run.length;\n\n if (start > cursor) {\n const piece = text.slice(cursor, start).trim();\n if (piece) out.push(piece);\n }\n\n if (HANGUL_RE.test(run)) {\n out.push(...segmentHangul(run));\n } else if (KANA_RE.test(run)) {\n out.push(...segmentKana(run));\n } else {\n out.push(...segmentHan(run));\n }\n\n cursor = end;\n }\n\n if (cursor < text.length) {\n const trailing = text.slice(cursor).trim();\n if (trailing) out.push(trailing);\n }\n\n return out;\n}\n\nexport function __resetCjkSegmenterStateForTests(): void {\n hintShown.clear();\n jiebaInstance = null;\n jiebaLoaded = false;\n jaSegmenterInstance = null;\n jaSegmenterLoaded = false;\n}\n","import { createHash } from \"node:crypto\";\nimport { hasCjk, segmentCjk } from \"./cjk-segmenter.js\";\n\nexport const KV = {\n sessions: \"mem:sessions\",\n observations: (sessionId: string) => `mem:obs:${sessionId}`,\n memories: \"mem:memories\",\n summaries: \"mem:summaries\",\n config: \"mem:config\",\n metrics: \"mem:metrics\",\n health: \"mem:health\",\n embeddings: (obsId: string) => `mem:emb:${obsId}`,\n bm25Index: \"mem:index:bm25\",\n relations: \"mem:relations\",\n profiles: \"mem:profiles\",\n claudeBridge: \"mem:claude-bridge\",\n graphNodes: \"mem:graph:nodes\",\n graphEdges: \"mem:graph:edges\",\n // #814: precomputed snapshot of the top-degree subgraph and aggregate\n // type counts. Saves /graph/query and /graph/stats from a full\n // kv.list enumeration over 75K+ node corpora, which exceeds the iii\n // invocation timeout and surfaces as \"Invocation stopped\" 500s.\n // Single fixed key (\"current\") so writes are read-modify-write under\n // the same keyed mutex as graph-extract.\n graphSnapshot: \"mem:graph:snapshot\",\n // #814 v2: targeted-lookup indexes so graph-extract never enumerates\n // the full nodes/edges scope. Each entry is a single small kv.get,\n // bounded payload — works at 75K+ nodes where kv.list would block\n // the worker event loop (37MB WS frame parse blocks heartbeat,\n // worker is declared dead before any Promise.race timer can fire).\n // - graphNameIndex: key `${type}|${name}` -> nodeId. Replaces the\n // existingNodes.find() O(n) dedup scan inside mem::graph-extract.\n // - graphEdgeKey: key `${src}|${tgt}|${type}` -> edgeId. Same for\n // edge dedup.\n // - graphNodeDegree: key nodeId -> incident-edge count. Read /\n // incremented on edge writes to maintain the snapshot top-N\n // ranking without scanning all edges.\n graphNameIndex: \"mem:graph:name-index\",\n graphEdgeKey: \"mem:graph:edge-key\",\n graphNodeDegree: \"mem:graph:node-degree\",\n semantic: \"mem:semantic\",\n procedural: \"mem:procedural\",\n teamShared: (teamId: string) => `mem:team:${teamId}:shared`,\n teamUsers: (teamId: string, userId: string) =>\n `mem:team:${teamId}:users:${userId}`,\n teamProfile: (teamId: string) => `mem:team:${teamId}:profile`,\n audit: \"mem:audit\",\n actions: \"mem:actions\",\n actionEdges: \"mem:action-edges\",\n leases: \"mem:leases\",\n routines: \"mem:routines\",\n routineRuns: \"mem:routine-runs\",\n signals: \"mem:signals\",\n checkpoints: \"mem:checkpoints\",\n mesh: \"mem:mesh\",\n sketches: \"mem:sketches\",\n facets: \"mem:facets\",\n sentinels: \"mem:sentinels\",\n crystals: \"mem:crystals\",\n lessons: \"mem:lessons\",\n insights: \"mem:insights\",\n graphEdgeHistory: \"mem:graph:edge-history\",\n enrichedChunks: (sessionId: string) => `mem:enriched:${sessionId}`,\n latentEmbeddings: (obsId: string) => `mem:latent:${obsId}`,\n retentionScores: \"mem:retention\",\n accessLog: \"mem:access\",\n imageRefs: \"mem:image-refs\",\n imageEmbeddings: \"mem:image-embeddings\",\n slots: \"mem:slots\",\n globalSlots: \"mem:slots:global\",\n state: \"mem:state\",\n commits: \"mem:commits\",\n // #771: tracks the most recent smart-search call per session, used by\n // the followup-rate diagnostic. Key = sessionId. TTL-swept hourly.\n recentSearches: \"mem:recent-searches\",\n} as const;\n\nexport const STREAM = {\n name: \"mem-live\",\n group: (sessionId: string) => sessionId,\n viewerGroup: \"viewer\",\n} as const;\n\nexport function generateId(prefix: string): string {\n const ts = Date.now().toString(36);\n const rand = crypto.randomUUID().replace(/-/g, \"\").slice(0, 12);\n return `${prefix}_${ts}_${rand}`;\n}\n\nexport function fingerprintId(prefix: string, content: string): string {\n const hash = createHash(\"sha256\").update(content).digest(\"hex\");\n return `${prefix}_${hash.slice(0, 16)}`;\n}\n\n// CJK/Japanese/Thai text carries no inter-word whitespace, so a plain\n// split(/\\s+/) collapses a whole sentence into one token and the two token\n// sets never overlap — dedup silently stops working. When either input\n// contains CJK we segment the CJK runs (word tokens via the shared\n// segmenter, plus character bigram shingles so near-identical strings still\n// overlap even when the optional segmenter deps fall back to whole-string).\n// ASCII text keeps the original word-level behavior.\nfunction jaccardTokens(text: string): Set<string> {\n if (!hasCjk(text)) {\n return new Set(text.split(/\\s+/).filter((t) => t.length > 2));\n }\n const tokens = new Set<string>();\n for (const raw of text.split(/\\s+/)) {\n if (!raw) continue;\n if (hasCjk(raw)) {\n for (const seg of segmentCjk(raw)) {\n if (seg) tokens.add(seg);\n }\n // Character bigram shingles keep the CJK signal even when the\n // segmenter returns the whole run: overlapping bigrams make\n // near-identical strings match while unrelated ones (\"北京\" vs\n // \"上海\") share none. Never drop short CJK tokens.\n const chars = Array.from(raw);\n if (chars.length === 1) {\n tokens.add(chars[0]);\n } else {\n for (let i = 0; i < chars.length - 1; i++) {\n tokens.add(chars[i] + chars[i + 1]);\n }\n }\n } else if (raw.length > 2) {\n tokens.add(raw);\n }\n }\n return tokens;\n}\n\nexport function jaccardSimilarity(a: string, b: string): number {\n const na = a.normalize(\"NFC\");\n const nb = b.normalize(\"NFC\");\n const setA = jaccardTokens(na);\n const setB = jaccardTokens(nb);\n // An empty token set carries no signal for the overlap metric. Very short\n // ASCII text (\"AI\", \"a b\", \"go\") tokenizes to nothing because words <=2\n // chars are dropped. Fall back to exact normalized equality so re-saving\n // the identical short memory still supersedes, while unrelated short\n // strings (\"AI\" vs \"ML\") correctly score 0. Returning 1 unconditionally\n // here (as an \"both empty\" shortcut once did) would let any short memory\n // falsely supersede another, silently marking a real memory not-latest.\n if (setA.size === 0 || setB.size === 0) {\n return na.trim().replace(/\\s+/g, \" \") === nb.trim().replace(/\\s+/g, \" \")\n ? 1\n : 0;\n }\n let intersection = 0;\n for (const word of setA) {\n if (setB.has(word)) intersection++;\n }\n return intersection / (setA.size + setB.size - intersection);\n}\n"],"mappings":"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"}
|