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
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
/**
|
|
171
|
+
* CJK-aware tokenizer for Jaccard similarity.
|
|
172
|
+
*
|
|
173
|
+
* For English/whitespace-delimited text we keep the original behaviour:
|
|
174
|
+
* split on whitespace, filter tokens longer than 2 characters.
|
|
175
|
+
*
|
|
176
|
+
* For CJK text (Chinese/Japanese/Korean) whitespace splitting produces
|
|
177
|
+
* near-disjoint sets because CJK text is typically written without spaces.
|
|
178
|
+
* We reuse the existing `segmentCjk` function and additionally emit
|
|
179
|
+
* character bigrams from CJK runs so that no-space Chinese text still
|
|
180
|
+
* produces useful overlap.
|
|
181
|
+
*/
|
|
182
|
+
function tokenizeForJaccard(text) {
|
|
183
|
+
if (!hasCjk(text)) return new Set(text.split(/\s+/).filter((t) => t.length > 2));
|
|
184
|
+
const segments = segmentCjk(text);
|
|
185
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
186
|
+
for (const seg of segments) {
|
|
187
|
+
const cleaned = seg.replace(/[^\p{L}\p{N}]/gu, "").toLowerCase();
|
|
188
|
+
if (cleaned.length > 2) tokens.add(cleaned);
|
|
189
|
+
const cjkChars = seg.match(/[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/gu);
|
|
190
|
+
if (cjkChars && cjkChars.length >= 2) for (let i = 0; i < cjkChars.length - 1; i++) tokens.add(cjkChars[i] + cjkChars[i + 1]);
|
|
191
|
+
}
|
|
192
|
+
return tokens;
|
|
193
|
+
}
|
|
194
|
+
function jaccardSimilarity(a, b) {
|
|
195
|
+
const setA = tokenizeForJaccard(a);
|
|
196
|
+
const setB = tokenizeForJaccard(b);
|
|
197
|
+
if (setA.size === 0 && setB.size === 0) return 1;
|
|
198
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
199
|
+
let intersection = 0;
|
|
200
|
+
for (const word of setA) if (setB.has(word)) intersection++;
|
|
201
|
+
return intersection / (setA.size + setB.size - intersection);
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { jaccardSimilarity as a, generateId as i, STREAM as n, hasCjk as o, fingerprintId as r, segmentCjk as s, KV as t };
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=schema-BQLY6d8C.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema-BQLY6d8C.mjs","names":[],"sources":["../src/state/cjk-segmenter.ts","../src/state/schema.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\r\n\r\nconst cjkRequire = createRequire(import.meta.url);\r\n\r\nconst CJK_RE = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/u;\r\nconst HAN_RE = /\\p{Script=Han}/u;\r\nconst KANA_RE = /[\\p{Script=Hiragana}\\p{Script=Katakana}]/u;\r\nconst HANGUL_RE = /\\p{Script=Hangul}/u;\r\nconst CJK_RUN_RE = /[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]+/gu;\r\nconst HANGUL_BLOCK_RE = /[가-]+/g;\r\n\r\ntype Script = \"han\" | \"kana\" | \"hangul\" | \"other\";\r\n\r\nconst hintShown = new Set<string>();\r\n\r\nexport function hasCjk(text: string): boolean {\r\n return CJK_RE.test(text);\r\n}\r\n\r\nexport function detectScript(text: string): Script {\r\n if (HAN_RE.test(text)) return \"han\";\r\n if (KANA_RE.test(text)) return \"kana\";\r\n if (HANGUL_RE.test(text)) return \"hangul\";\r\n return \"other\";\r\n}\r\n\r\nfunction showHintOnce(key: string, message: string): void {\r\n if (hintShown.has(key)) return;\r\n hintShown.add(key);\r\n if (typeof process !== \"undefined\" && process.stderr?.write) {\r\n process.stderr.write(`agentmemory: ${message}\\n`);\r\n }\r\n}\r\n\r\ninterface JiebaInstance {\r\n cut(text: string, hmm?: boolean): string[];\r\n}\r\n\r\nlet jiebaInstance: JiebaInstance | null = null;\r\nlet jiebaLoaded = false;\r\n\r\nfunction getJieba(): JiebaInstance | null {\r\n if (jiebaLoaded) return jiebaInstance;\r\n jiebaLoaded = true;\r\n try {\r\n const mod = cjkRequire(\"@node-rs/jieba\") as {\r\n Jieba: {\r\n new (): JiebaInstance;\r\n withDict(dict: Uint8Array): JiebaInstance;\r\n };\r\n };\r\n try {\r\n const dictMod = cjkRequire(\"@node-rs/jieba/dict\") as { dict: Uint8Array };\r\n jiebaInstance = mod.Jieba.withDict(dictMod.dict);\r\n } catch {\r\n jiebaInstance = new mod.Jieba();\r\n }\r\n return jiebaInstance;\r\n } catch {\r\n showHintOnce(\r\n \"jieba\",\r\n \"install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization\",\r\n );\r\n return null;\r\n }\r\n}\r\n\r\ninterface JaSegmenter {\r\n segment(text: string): string[];\r\n}\r\n\r\nlet jaSegmenterInstance: JaSegmenter | null = null;\r\nlet jaSegmenterLoaded = false;\r\n\r\nfunction getJaSegmenter(): JaSegmenter | null {\r\n if (jaSegmenterLoaded) return jaSegmenterInstance;\r\n jaSegmenterLoaded = true;\r\n try {\r\n const Ctor = cjkRequire(\"tiny-segmenter\") as new () => JaSegmenter;\r\n jaSegmenterInstance = new Ctor();\r\n return jaSegmenterInstance;\r\n } catch {\r\n showHintOnce(\r\n \"tiny-segmenter\",\r\n \"install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization\",\r\n );\r\n return null;\r\n }\r\n}\r\n\r\nfunction cleanTokens(tokens: string[]): string[] {\r\n const out: string[] = [];\r\n for (const t of tokens) {\r\n const trimmed = t.trim();\r\n if (trimmed) out.push(trimmed);\r\n }\r\n return out;\r\n}\r\n\r\nfunction segmentHan(text: string): string[] {\r\n const j = getJieba();\r\n if (!j) return [text];\r\n try {\r\n return cleanTokens(j.cut(text, true));\r\n } catch {\r\n return [text];\r\n }\r\n}\r\n\r\nfunction segmentKana(text: string): string[] {\r\n const s = getJaSegmenter();\r\n if (!s) return [text];\r\n try {\r\n return cleanTokens(s.segment(text));\r\n } catch {\r\n return [text];\r\n }\r\n}\r\n\r\nfunction segmentHangul(text: string): string[] {\r\n const out: string[] = [];\r\n for (const m of text.matchAll(HANGUL_BLOCK_RE)) {\r\n if (m[0]) out.push(m[0]);\r\n }\r\n return out;\r\n}\r\n\r\nexport function segmentCjk(text: string): string[] {\r\n if (!hasCjk(text)) return [text];\r\n\r\n const out: string[] = [];\r\n let cursor = 0;\r\n\r\n for (const m of text.matchAll(CJK_RUN_RE)) {\r\n const start = m.index ?? 0;\r\n const run = m[0];\r\n const end = start + run.length;\r\n\r\n if (start > cursor) {\r\n const piece = text.slice(cursor, start).trim();\r\n if (piece) out.push(piece);\r\n }\r\n\r\n if (HANGUL_RE.test(run)) {\r\n out.push(...segmentHangul(run));\r\n } else if (KANA_RE.test(run)) {\r\n out.push(...segmentKana(run));\r\n } else {\r\n out.push(...segmentHan(run));\r\n }\r\n\r\n cursor = end;\r\n }\r\n\r\n if (cursor < text.length) {\r\n const trailing = text.slice(cursor).trim();\r\n if (trailing) out.push(trailing);\r\n }\r\n\r\n return out;\r\n}\r\n\r\nexport function __resetCjkSegmenterStateForTests(): void {\r\n hintShown.clear();\r\n jiebaInstance = null;\r\n jiebaLoaded = false;\r\n jaSegmenterInstance = null;\r\n jaSegmenterLoaded = false;\r\n}\r\n","import { createHash } from \"node:crypto\";\r\nimport { hasCjk, segmentCjk } from \"./cjk-segmenter.js\";\r\n\r\nexport const KV = {\r\n sessions: \"mem:sessions\",\r\n observations: (sessionId: string) => `mem:obs:${sessionId}`,\r\n memories: \"mem:memories\",\r\n summaries: \"mem:summaries\",\r\n config: \"mem:config\",\r\n metrics: \"mem:metrics\",\r\n health: \"mem:health\",\r\n embeddings: (obsId: string) => `mem:emb:${obsId}`,\r\n bm25Index: \"mem:index:bm25\",\r\n relations: \"mem:relations\",\r\n profiles: \"mem:profiles\",\r\n claudeBridge: \"mem:claude-bridge\",\r\n graphNodes: \"mem:graph:nodes\",\r\n graphEdges: \"mem:graph:edges\",\r\n // #814: precomputed snapshot of the top-degree subgraph and aggregate\r\n // type counts. Saves /graph/query and /graph/stats from a full\r\n // kv.list enumeration over 75K+ node corpora, which exceeds the iii\r\n // invocation timeout and surfaces as \"Invocation stopped\" 500s.\r\n // Single fixed key (\"current\") so writes are read-modify-write under\r\n // the same keyed mutex as graph-extract.\r\n graphSnapshot: \"mem:graph:snapshot\",\r\n // #814 v2: targeted-lookup indexes so graph-extract never enumerates\r\n // the full nodes/edges scope. Each entry is a single small kv.get,\r\n // bounded payload — works at 75K+ nodes where kv.list would block\r\n // the worker event loop (37MB WS frame parse blocks heartbeat,\r\n // worker is declared dead before any Promise.race timer can fire).\r\n // - graphNameIndex: key `${type}|${name}` -> nodeId. Replaces the\r\n // existingNodes.find() O(n) dedup scan inside mem::graph-extract.\r\n // - graphEdgeKey: key `${src}|${tgt}|${type}` -> edgeId. Same for\r\n // edge dedup.\r\n // - graphNodeDegree: key nodeId -> incident-edge count. Read /\r\n // incremented on edge writes to maintain the snapshot top-N\r\n // ranking without scanning all edges.\r\n graphNameIndex: \"mem:graph:name-index\",\r\n graphEdgeKey: \"mem:graph:edge-key\",\r\n graphNodeDegree: \"mem:graph:node-degree\",\r\n semantic: \"mem:semantic\",\r\n procedural: \"mem:procedural\",\r\n teamShared: (teamId: string) => `mem:team:${teamId}:shared`,\r\n teamUsers: (teamId: string, userId: string) =>\r\n `mem:team:${teamId}:users:${userId}`,\r\n teamProfile: (teamId: string) => `mem:team:${teamId}:profile`,\r\n audit: \"mem:audit\",\r\n actions: \"mem:actions\",\r\n actionEdges: \"mem:action-edges\",\r\n leases: \"mem:leases\",\r\n routines: \"mem:routines\",\r\n routineRuns: \"mem:routine-runs\",\r\n signals: \"mem:signals\",\r\n checkpoints: \"mem:checkpoints\",\r\n mesh: \"mem:mesh\",\r\n sketches: \"mem:sketches\",\r\n facets: \"mem:facets\",\r\n sentinels: \"mem:sentinels\",\r\n crystals: \"mem:crystals\",\r\n lessons: \"mem:lessons\",\r\n insights: \"mem:insights\",\r\n graphEdgeHistory: \"mem:graph:edge-history\",\r\n enrichedChunks: (sessionId: string) => `mem:enriched:${sessionId}`,\r\n latentEmbeddings: (obsId: string) => `mem:latent:${obsId}`,\r\n retentionScores: \"mem:retention\",\r\n accessLog: \"mem:access\",\r\n imageRefs: \"mem:image-refs\",\r\n imageEmbeddings: \"mem:image-embeddings\",\r\n slots: \"mem:slots\",\r\n globalSlots: \"mem:slots:global\",\r\n state: \"mem:state\",\r\n commits: \"mem:commits\",\r\n // #771: tracks the most recent smart-search call per session, used by\r\n // the followup-rate diagnostic. Key = sessionId. TTL-swept hourly.\r\n recentSearches: \"mem:recent-searches\",\r\n} as const;\r\n\r\nexport const STREAM = {\r\n name: \"mem-live\",\r\n group: (sessionId: string) => sessionId,\r\n viewerGroup: \"viewer\",\r\n} as const;\r\n\r\nexport function generateId(prefix: string): string {\r\n const ts = Date.now().toString(36);\r\n const rand = crypto.randomUUID().replace(/-/g, \"\").slice(0, 12);\r\n return `${prefix}_${ts}_${rand}`;\r\n}\r\n\r\nexport function fingerprintId(prefix: string, content: string): string {\r\n const hash = createHash(\"sha256\").update(content).digest(\"hex\");\r\n return `${prefix}_${hash.slice(0, 16)}`;\r\n}\r\n\r\n/**\r\n * CJK-aware tokenizer for Jaccard similarity.\r\n *\r\n * For English/whitespace-delimited text we keep the original behaviour:\r\n * split on whitespace, filter tokens longer than 2 characters.\r\n *\r\n * For CJK text (Chinese/Japanese/Korean) whitespace splitting produces\r\n * near-disjoint sets because CJK text is typically written without spaces.\r\n * We reuse the existing `segmentCjk` function and additionally emit\r\n * character bigrams from CJK runs so that no-space Chinese text still\r\n * produces useful overlap.\r\n */\r\nfunction tokenizeForJaccard(text: string): Set<string> {\r\n // Fast path: no CJK characters → original whitespace split\r\n if (!hasCjk(text)) {\r\n return new Set(text.split(/\\s+/).filter((t) => t.length > 2));\r\n }\r\n\r\n // CJK path: segment + add character bigrams for overlap\r\n const segments = segmentCjk(text);\r\n const tokens = new Set<string>();\r\n\r\n for (const seg of segments) {\r\n // Keep the whole segment as a token (works for word-segmented Chinese\r\n // via jieba, and for English fragments in mixed text).\r\n const cleaned = seg.replace(/[^\\p{L}\\p{N}]/gu, \"\").toLowerCase();\r\n if (cleaned.length > 2) {\r\n tokens.add(cleaned);\r\n }\r\n\r\n // Add CJK character bigrams from each segment so unsegmented CJK\r\n // text can still produce meaningful overlap.\r\n const cjkChars = seg.match(/[\\p{Script=Han}\\p{Script=Hiragana}\\p{Script=Katakana}\\p{Script=Hangul}]/gu);\r\n if (cjkChars && cjkChars.length >= 2) {\r\n for (let i = 0; i < cjkChars.length - 1; i++) {\r\n tokens.add(cjkChars[i] + cjkChars[i + 1]);\r\n }\r\n }\r\n }\r\n\r\n return tokens;\r\n}\r\n\r\nexport function jaccardSimilarity(a: string, b: string): number {\r\n const setA = tokenizeForJaccard(a);\r\n const setB = tokenizeForJaccard(b);\r\n if (setA.size === 0 && setB.size === 0) return 1;\r\n if (setA.size === 0 || setB.size === 0) return 0;\r\n let intersection = 0;\r\n for (const word of setA) {\r\n if (setB.has(word)) intersection++;\r\n }\r\n return intersection / (setA.size + setB.size - intersection);\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { a as jaccardSimilarity, i as generateId, n as STREAM, r as fingerprintId, t as KV } from "./schema-
|
|
1
|
+
import { a as jaccardSimilarity, i as generateId, n as STREAM, o as hasCjk, r as fingerprintId, s as segmentCjk, t as KV } from "./schema-BQLY6d8C.mjs";
|
|
2
2
|
import { n as logger, t as bootLog } from "./logger-BEB7pCI9.mjs";
|
|
3
3
|
import { i as VERSION, n as getAllTools, r as getVisibleTools } from "./tools-registry-DCt2KAWA.mjs";
|
|
4
4
|
import { a as isManagedImagePath, i as getMaxBytes, n as IMAGES_DIR, o as saveImageToDisk, r as deleteImage, t as withKeyedLock } from "./keyed-mutex-DTHtcZrt.mjs";
|
|
5
5
|
import { _ as loadFallbackConfig, a as getEnvVar, c as isAgentScopeIsolated, d as isContextInjectionEnabled, f as isDropStaleIndexEnabled, g as loadEmbeddingConfig, h as loadConfig, i as getConsolidationDecayDays, l as isAutoCompressEnabled, m as loadClaudeBridgeConfig, n as detectLlmProviderKind, o as getFollowupWindowSeconds, p as isGraphExtractionEnabled, r as getAgentId, t as detectEmbeddingProvider, u as isConsolidationEnabled, v as loadSnapshotConfig, y as loadTeamConfig } from "./config-ChC9EtgV.mjs";
|
|
6
|
-
import { createRequire } from "node:module";
|
|
7
6
|
import { execFile } from "node:child_process";
|
|
8
7
|
import { constants, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
9
8
|
import { basename, dirname, extname, join, resolve, sep } from "node:path";
|
|
@@ -609,25 +608,25 @@ function l2Normalize(vec) {
|
|
|
609
608
|
}
|
|
610
609
|
//#endregion
|
|
611
610
|
//#region src/providers/embedding/openai.ts
|
|
612
|
-
const DEFAULT_MODEL$
|
|
611
|
+
const DEFAULT_MODEL$2 = "text-embedding-3-small";
|
|
613
612
|
/**
|
|
614
613
|
* Known OpenAI embedding model dimensions. Extend as new models ship.
|
|
615
614
|
* Override in any case via OPENAI_EMBEDDING_DIMENSIONS for custom or
|
|
616
615
|
* self-hosted OpenAI-compatible endpoints returning non-standard sizes.
|
|
617
616
|
*/
|
|
618
|
-
const MODEL_DIMENSIONS = {
|
|
617
|
+
const MODEL_DIMENSIONS$1 = {
|
|
619
618
|
"text-embedding-3-small": 1536,
|
|
620
619
|
"text-embedding-3-large": 3072,
|
|
621
620
|
"text-embedding-ada-002": 1536
|
|
622
621
|
};
|
|
623
|
-
const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL$
|
|
624
|
-
function resolveDimensions(model, override) {
|
|
622
|
+
const DEFAULT_DIMENSIONS$1 = MODEL_DIMENSIONS$1[DEFAULT_MODEL$2] ?? 1536;
|
|
623
|
+
function resolveDimensions$1(model, override) {
|
|
625
624
|
if (override !== void 0 && override.trim().length > 0) {
|
|
626
625
|
const parsed = parseInt(override, 10);
|
|
627
626
|
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`OPENAI_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`);
|
|
628
627
|
return parsed;
|
|
629
628
|
}
|
|
630
|
-
return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS;
|
|
629
|
+
return MODEL_DIMENSIONS$1[model] ?? DEFAULT_DIMENSIONS$1;
|
|
631
630
|
}
|
|
632
631
|
/**
|
|
633
632
|
* OpenAI-compatible embedding provider.
|
|
@@ -677,8 +676,8 @@ var OpenAIEmbeddingProvider = class {
|
|
|
677
676
|
this.apiKey = apiKey || getEnvVar("OPENAI_EMBEDDING_API_KEY") || getEnvVar("OPENAI_API_KEY") || "";
|
|
678
677
|
if (!this.apiKey) throw new Error("API key is required (via constructor, OPENAI_EMBEDDING_API_KEY, or OPENAI_API_KEY)");
|
|
679
678
|
this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_EMBEDDING_BASE_URL") || getEnvVar("OPENAI_BASE_URL"));
|
|
680
|
-
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$
|
|
681
|
-
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
679
|
+
this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$2;
|
|
680
|
+
this.dimensions = resolveDimensions$1(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
|
|
682
681
|
this.isAzure = detectAzure(this.baseUrl);
|
|
683
682
|
this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || "2024-08-01-preview";
|
|
684
683
|
}
|
|
@@ -775,15 +774,36 @@ var CohereEmbeddingProvider = class {
|
|
|
775
774
|
//#endregion
|
|
776
775
|
//#region src/providers/embedding/openrouter.ts
|
|
777
776
|
const API_URL = "https://openrouter.ai/api/v1/embeddings";
|
|
777
|
+
/**
|
|
778
|
+
* Known OpenRouter embedding model dimensions. Extend as new models ship.
|
|
779
|
+
* Override via OPENROUTER_EMBEDDING_DIMENSIONS for models not listed here
|
|
780
|
+
* or custom OpenAI-compatible endpoints returning non-standard sizes.
|
|
781
|
+
*/
|
|
782
|
+
const MODEL_DIMENSIONS = {
|
|
783
|
+
"openai/text-embedding-3-small": 1536,
|
|
784
|
+
"openai/text-embedding-3-large": 3072,
|
|
785
|
+
"openai/text-embedding-ada-002": 1536
|
|
786
|
+
};
|
|
787
|
+
const DEFAULT_MODEL$1 = "openai/text-embedding-3-small";
|
|
788
|
+
const DEFAULT_DIMENSIONS = MODEL_DIMENSIONS[DEFAULT_MODEL$1] ?? 1536;
|
|
789
|
+
function resolveDimensions(model, override) {
|
|
790
|
+
if (override !== void 0 && override.trim().length > 0) {
|
|
791
|
+
const parsed = parseInt(override, 10);
|
|
792
|
+
if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`OPENROUTER_EMBEDDING_DIMENSIONS must be a positive integer, got: ${override}`);
|
|
793
|
+
return parsed;
|
|
794
|
+
}
|
|
795
|
+
return MODEL_DIMENSIONS[model] ?? DEFAULT_DIMENSIONS;
|
|
796
|
+
}
|
|
778
797
|
var OpenRouterEmbeddingProvider = class {
|
|
779
798
|
name = "openrouter";
|
|
780
|
-
dimensions
|
|
799
|
+
dimensions;
|
|
781
800
|
apiKey;
|
|
782
801
|
model;
|
|
783
802
|
constructor(apiKey) {
|
|
784
803
|
this.apiKey = apiKey || getEnvVar("OPENROUTER_API_KEY") || "";
|
|
785
804
|
if (!this.apiKey) throw new Error("OPENROUTER_API_KEY is required");
|
|
786
|
-
this.model = getEnvVar("OPENROUTER_EMBEDDING_MODEL") ||
|
|
805
|
+
this.model = getEnvVar("OPENROUTER_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
|
|
806
|
+
this.dimensions = resolveDimensions(this.model, getEnvVar("OPENROUTER_EMBEDDING_DIMENSIONS"));
|
|
787
807
|
}
|
|
788
808
|
async embed(text) {
|
|
789
809
|
const [result] = await this.embedBatch([text]);
|
|
@@ -2072,109 +2092,6 @@ function getSynonyms(stemmedTerm) {
|
|
|
2072
2092
|
return syns ? [...syns] : [];
|
|
2073
2093
|
}
|
|
2074
2094
|
//#endregion
|
|
2075
|
-
//#region src/state/cjk-segmenter.ts
|
|
2076
|
-
const cjkRequire = createRequire(import.meta.url);
|
|
2077
|
-
const CJK_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]/u;
|
|
2078
|
-
const KANA_RE = /[\p{Script=Hiragana}\p{Script=Katakana}]/u;
|
|
2079
|
-
const HANGUL_RE = /\p{Script=Hangul}/u;
|
|
2080
|
-
const CJK_RUN_RE = /[\p{Script=Han}\p{Script=Hiragana}\p{Script=Katakana}\p{Script=Hangul}]+/gu;
|
|
2081
|
-
const HANGUL_BLOCK_RE = /[가-]+/g;
|
|
2082
|
-
const hintShown = /* @__PURE__ */ new Set();
|
|
2083
|
-
function hasCjk(text) {
|
|
2084
|
-
return CJK_RE.test(text);
|
|
2085
|
-
}
|
|
2086
|
-
function showHintOnce(key, message) {
|
|
2087
|
-
if (hintShown.has(key)) return;
|
|
2088
|
-
hintShown.add(key);
|
|
2089
|
-
if (typeof process !== "undefined" && process.stderr?.write) process.stderr.write(`agentmemory: ${message}\n`);
|
|
2090
|
-
}
|
|
2091
|
-
let jiebaInstance = null;
|
|
2092
|
-
let jiebaLoaded = false;
|
|
2093
|
-
function getJieba() {
|
|
2094
|
-
if (jiebaLoaded) return jiebaInstance;
|
|
2095
|
-
jiebaLoaded = true;
|
|
2096
|
-
try {
|
|
2097
|
-
const mod = cjkRequire("@node-rs/jieba");
|
|
2098
|
-
try {
|
|
2099
|
-
const dictMod = cjkRequire("@node-rs/jieba/dict");
|
|
2100
|
-
jiebaInstance = mod.Jieba.withDict(dictMod.dict);
|
|
2101
|
-
} catch {
|
|
2102
|
-
jiebaInstance = new mod.Jieba();
|
|
2103
|
-
}
|
|
2104
|
-
return jiebaInstance;
|
|
2105
|
-
} catch {
|
|
2106
|
-
showHintOnce("jieba", "install @node-rs/jieba to improve Chinese search; falling back to whole-string tokenization");
|
|
2107
|
-
return null;
|
|
2108
|
-
}
|
|
2109
|
-
}
|
|
2110
|
-
let jaSegmenterInstance = null;
|
|
2111
|
-
let jaSegmenterLoaded = false;
|
|
2112
|
-
function getJaSegmenter() {
|
|
2113
|
-
if (jaSegmenterLoaded) return jaSegmenterInstance;
|
|
2114
|
-
jaSegmenterLoaded = true;
|
|
2115
|
-
try {
|
|
2116
|
-
jaSegmenterInstance = new (cjkRequire("tiny-segmenter"))();
|
|
2117
|
-
return jaSegmenterInstance;
|
|
2118
|
-
} catch {
|
|
2119
|
-
showHintOnce("tiny-segmenter", "install tiny-segmenter to improve Japanese search; falling back to whole-string tokenization");
|
|
2120
|
-
return null;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
function cleanTokens(tokens) {
|
|
2124
|
-
const out = [];
|
|
2125
|
-
for (const t of tokens) {
|
|
2126
|
-
const trimmed = t.trim();
|
|
2127
|
-
if (trimmed) out.push(trimmed);
|
|
2128
|
-
}
|
|
2129
|
-
return out;
|
|
2130
|
-
}
|
|
2131
|
-
function segmentHan(text) {
|
|
2132
|
-
const j = getJieba();
|
|
2133
|
-
if (!j) return [text];
|
|
2134
|
-
try {
|
|
2135
|
-
return cleanTokens(j.cut(text, true));
|
|
2136
|
-
} catch {
|
|
2137
|
-
return [text];
|
|
2138
|
-
}
|
|
2139
|
-
}
|
|
2140
|
-
function segmentKana(text) {
|
|
2141
|
-
const s = getJaSegmenter();
|
|
2142
|
-
if (!s) return [text];
|
|
2143
|
-
try {
|
|
2144
|
-
return cleanTokens(s.segment(text));
|
|
2145
|
-
} catch {
|
|
2146
|
-
return [text];
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
function segmentHangul(text) {
|
|
2150
|
-
const out = [];
|
|
2151
|
-
for (const m of text.matchAll(HANGUL_BLOCK_RE)) if (m[0]) out.push(m[0]);
|
|
2152
|
-
return out;
|
|
2153
|
-
}
|
|
2154
|
-
function segmentCjk(text) {
|
|
2155
|
-
if (!hasCjk(text)) return [text];
|
|
2156
|
-
const out = [];
|
|
2157
|
-
let cursor = 0;
|
|
2158
|
-
for (const m of text.matchAll(CJK_RUN_RE)) {
|
|
2159
|
-
const start = m.index ?? 0;
|
|
2160
|
-
const run = m[0];
|
|
2161
|
-
const end = start + run.length;
|
|
2162
|
-
if (start > cursor) {
|
|
2163
|
-
const piece = text.slice(cursor, start).trim();
|
|
2164
|
-
if (piece) out.push(piece);
|
|
2165
|
-
}
|
|
2166
|
-
if (HANGUL_RE.test(run)) out.push(...segmentHangul(run));
|
|
2167
|
-
else if (KANA_RE.test(run)) out.push(...segmentKana(run));
|
|
2168
|
-
else out.push(...segmentHan(run));
|
|
2169
|
-
cursor = end;
|
|
2170
|
-
}
|
|
2171
|
-
if (cursor < text.length) {
|
|
2172
|
-
const trailing = text.slice(cursor).trim();
|
|
2173
|
-
if (trailing) out.push(trailing);
|
|
2174
|
-
}
|
|
2175
|
-
return out;
|
|
2176
|
-
}
|
|
2177
|
-
//#endregion
|
|
2178
2095
|
//#region src/state/search-index.ts
|
|
2179
2096
|
var SearchIndex = class SearchIndex {
|
|
2180
2097
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -3338,7 +3255,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3338
3255
|
if (pendingImageData && (pendingImageData.startsWith("data:image/") || pendingImageData.startsWith("iVBORw0KGgo") || pendingImageData.startsWith("/9j/"))) {
|
|
3339
3256
|
const { filePath, bytesWritten } = await saveImageToDisk(pendingImageData);
|
|
3340
3257
|
raw.imageData = filePath;
|
|
3341
|
-
const { incrementImageRef } = await import("./image-refs-
|
|
3258
|
+
const { incrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
3342
3259
|
await incrementImageRef(kv, filePath);
|
|
3343
3260
|
sdk.trigger({
|
|
3344
3261
|
function_id: "mem::disk-size-delta",
|
|
@@ -3359,7 +3276,7 @@ function registerObserveFunction(sdk, kv, dedupMap, maxObservationsPerSession) {
|
|
|
3359
3276
|
await kv.set(KV.observations(payload.sessionId), obsId, raw);
|
|
3360
3277
|
} catch (error) {
|
|
3361
3278
|
if (raw.imageData) try {
|
|
3362
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
3279
|
+
const { decrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
3363
3280
|
await decrementImageRef(kv, sdk, raw.imageData);
|
|
3364
3281
|
} catch (rollbackError) {
|
|
3365
3282
|
logger.error("Failed to roll back image ref after observation write failure", {
|
|
@@ -3533,7 +3450,7 @@ function registerImageQuotaCleanup(sdk, kv) {
|
|
|
3533
3450
|
await withKeyedLock(`imgRef:${f.filePath}`, async () => {
|
|
3534
3451
|
let refCount;
|
|
3535
3452
|
try {
|
|
3536
|
-
const { getImageRefCount } = await import("./image-refs-
|
|
3453
|
+
const { getImageRefCount } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
3537
3454
|
refCount = await getImageRefCount(kv, f.filePath);
|
|
3538
3455
|
} catch (err) {
|
|
3539
3456
|
logger.error("Failed to read refCount; skipping eviction", {
|
|
@@ -4945,7 +4862,7 @@ function registerSummarizeFunction(sdk, kv, provider, metricsStore) {
|
|
|
4945
4862
|
error: "no_observations"
|
|
4946
4863
|
};
|
|
4947
4864
|
}
|
|
4948
|
-
if (provider.name === "noop") {
|
|
4865
|
+
if (provider.name === "noop" || provider.name === "resilient(noop)") {
|
|
4949
4866
|
logger.info("Summarize skipped — no LLM provider configured", { sessionId });
|
|
4950
4867
|
return {
|
|
4951
4868
|
success: false,
|
|
@@ -5556,6 +5473,34 @@ function registerPatternsFunction(sdk, kv) {
|
|
|
5556
5473
|
});
|
|
5557
5474
|
}
|
|
5558
5475
|
//#endregion
|
|
5476
|
+
//#region src/state/memory-dedup.ts
|
|
5477
|
+
/**
|
|
5478
|
+
* Negation guard for memory dedup.
|
|
5479
|
+
*
|
|
5480
|
+
* Prevents "do X" from superseding "do not X" (and vice versa) even when
|
|
5481
|
+
* the two strings share enough tokens to exceed the Jaccard threshold.
|
|
5482
|
+
*
|
|
5483
|
+
* The guard is deliberately narrow and conservative:
|
|
5484
|
+
* - Only triggers when one side has a negation marker and the other does not.
|
|
5485
|
+
* - Supports English ("not", "never", "don't", "do not") and CJK
|
|
5486
|
+
* ("不要", "别", "無", "なし", "않") markers.
|
|
5487
|
+
* - If BOTH sides have negation markers, they are treated as compatible
|
|
5488
|
+
* (both say "don't do X") and the guard does not fire.
|
|
5489
|
+
*/
|
|
5490
|
+
const EN_NEGATION = /\b(not|never|don't|do not|cannot|should not|must not|no)\b/i;
|
|
5491
|
+
const CJK_NEGATION = /不要|别|勿|無|无|不|なし|않|안|못/i;
|
|
5492
|
+
function hasNegation(text) {
|
|
5493
|
+
return EN_NEGATION.test(text) || CJK_NEGATION.test(text);
|
|
5494
|
+
}
|
|
5495
|
+
/**
|
|
5496
|
+
* Returns true if the two texts appear to be in a negation conflict
|
|
5497
|
+
* (one says do X, the other says do NOT do X) and should NOT be superseded.
|
|
5498
|
+
*/
|
|
5499
|
+
function isNegationConflict(a, b) {
|
|
5500
|
+
if (hasNegation(a) === hasNegation(b)) return false;
|
|
5501
|
+
return true;
|
|
5502
|
+
}
|
|
5503
|
+
//#endregion
|
|
5559
5504
|
//#region src/functions/remember.ts
|
|
5560
5505
|
function registerRememberFunction(sdk, kv) {
|
|
5561
5506
|
sdk.registerFunction("mem::remember", async (data) => {
|
|
@@ -5594,7 +5539,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5594
5539
|
for (const existing of existingMemories) {
|
|
5595
5540
|
if (existing.isLatest === false) continue;
|
|
5596
5541
|
if (project && existing.project && existing.project !== project) continue;
|
|
5597
|
-
if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7) {
|
|
5542
|
+
if (jaccardSimilarity(lowerContent, existing.content.toLowerCase()) > .7 && !isNegationConflict(lowerContent, existing.content.toLowerCase())) {
|
|
5598
5543
|
supersededId = existing.id;
|
|
5599
5544
|
supersededVersion = existing.version ?? 1;
|
|
5600
5545
|
supersededMemory = existing;
|
|
@@ -5744,7 +5689,7 @@ function registerRememberFunction(sdk, kv) {
|
|
|
5744
5689
|
const deletedMemoryIds = [];
|
|
5745
5690
|
const deletedObservationIds = [];
|
|
5746
5691
|
let deletedSession = false;
|
|
5747
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5692
|
+
const { decrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
5748
5693
|
if (data.memoryId) {
|
|
5749
5694
|
const mem = await kv.get(KV.memories, data.memoryId);
|
|
5750
5695
|
await kv.delete(KV.memories, data.memoryId);
|
|
@@ -5851,7 +5796,7 @@ async function runRecoveredSessionConsolidation(sdk) {
|
|
|
5851
5796
|
function registerEvictFunction(sdk, kv) {
|
|
5852
5797
|
sdk.registerFunction("mem::evict", async (data) => {
|
|
5853
5798
|
const dryRun = data?.dryRun ?? false;
|
|
5854
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
5799
|
+
const { decrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
5855
5800
|
const configOverride = await kv.get(KV.config, "eviction").catch(() => null);
|
|
5856
5801
|
const cfg = {
|
|
5857
5802
|
...DEFAULTS$1,
|
|
@@ -6666,7 +6611,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
6666
6611
|
sdk.registerFunction("mem::auto-forget", async (data) => {
|
|
6667
6612
|
const dryRun = data?.dryRun ?? false;
|
|
6668
6613
|
const now = Date.now();
|
|
6669
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
6614
|
+
const { decrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
6670
6615
|
const result = {
|
|
6671
6616
|
ttlExpired: [],
|
|
6672
6617
|
contradictions: [],
|
|
@@ -13804,7 +13749,7 @@ function registerRetentionFunctions(sdk, kv) {
|
|
|
13804
13749
|
const threshold = typeof data?.threshold === "number" && Number.isFinite(data.threshold) ? data.threshold : DEFAULT_DECAY.tierThresholds.cold;
|
|
13805
13750
|
const maxEvictRaw = typeof data?.maxEvict === "number" && Number.isInteger(data.maxEvict) ? data.maxEvict : 50;
|
|
13806
13751
|
const maxEvict = Math.min(1e3, Math.max(0, maxEvictRaw));
|
|
13807
|
-
const { decrementImageRef } = await import("./image-refs-
|
|
13752
|
+
const { decrementImageRef } = await import("./image-refs-Cc7-ua5j.mjs");
|
|
13808
13753
|
const candidates = (await kv.list(KV.retentionScores)).filter((s) => s.score < threshold).sort((a, b) => a.score - b.score).slice(0, maxEvict);
|
|
13809
13754
|
if (data?.dryRun) return {
|
|
13810
13755
|
success: true,
|
|
@@ -15017,6 +14962,10 @@ function parseOptionalInt(raw) {
|
|
|
15017
14962
|
const n = typeof raw === "number" ? raw : parseInt(String(raw), 10);
|
|
15018
14963
|
return Number.isFinite(n) ? n : void 0;
|
|
15019
14964
|
}
|
|
14965
|
+
function parsePositiveLimit(raw) {
|
|
14966
|
+
const n = parseOptionalInt(raw);
|
|
14967
|
+
return n !== void 0 && n > 0 ? n : void 0;
|
|
14968
|
+
}
|
|
15020
14969
|
function checkAuth(req, secret) {
|
|
15021
14970
|
if (!secret) return null;
|
|
15022
14971
|
const auth = req.headers?.["authorization"] || req.headers?.["Authorization"];
|
|
@@ -15405,11 +15354,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15405
15354
|
if (authErr) return authErr;
|
|
15406
15355
|
const sessions = await kv.list(KV.sessions);
|
|
15407
15356
|
sessions.sort((a, b) => (b.startedAt || "").localeCompare(a.startedAt || ""));
|
|
15357
|
+
const limit = parsePositiveLimit(req.query_params?.["limit"]);
|
|
15408
15358
|
return {
|
|
15409
15359
|
status_code: 200,
|
|
15410
15360
|
body: {
|
|
15411
15361
|
success: true,
|
|
15412
|
-
sessions
|
|
15362
|
+
sessions: limit !== void 0 ? sessions.slice(0, limit) : sessions
|
|
15413
15363
|
}
|
|
15414
15364
|
};
|
|
15415
15365
|
});
|
|
@@ -15681,10 +15631,12 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15681
15631
|
const wildcardAgent = normalizedAgentId === "*";
|
|
15682
15632
|
const filterAgentId = wildcardAgent ? void 0 : (normalizedAgentId && !wildcardAgent ? normalizedAgentId : void 0) ?? (isAgentScopeIsolated() ? getAgentId() : void 0);
|
|
15683
15633
|
const filtered = filterAgentId ? sessions.filter((s) => s.agentId === filterAgentId) : sessions;
|
|
15684
|
-
const
|
|
15634
|
+
const limit = parsePositiveLimit(req.query_params?.["limit"]);
|
|
15635
|
+
const sliced = limit !== void 0 ? filtered.slice(0, limit) : filtered;
|
|
15636
|
+
const summaries = await Promise.all(sliced.map((s) => kv.get(KV.summaries, s.id).catch(() => null)));
|
|
15685
15637
|
return {
|
|
15686
15638
|
status_code: 200,
|
|
15687
|
-
body: { sessions:
|
|
15639
|
+
body: { sessions: sliced.map((s, i) => summaries[i] ? {
|
|
15688
15640
|
...s,
|
|
15689
15641
|
summary: summaries[i]
|
|
15690
15642
|
} : s) }
|
|
@@ -15881,13 +15833,22 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
15881
15833
|
sdk.registerFunction("api::consolidate", async (req) => {
|
|
15882
15834
|
const authErr = checkAuth(req, secret);
|
|
15883
15835
|
if (authErr) return authErr;
|
|
15884
|
-
|
|
15885
|
-
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15836
|
+
try {
|
|
15837
|
+
return {
|
|
15838
|
+
status_code: 200,
|
|
15839
|
+
body: await sdk.trigger({
|
|
15840
|
+
function_id: "mem::consolidate",
|
|
15841
|
+
payload: req.body
|
|
15842
|
+
})
|
|
15843
|
+
};
|
|
15844
|
+
} catch (err) {
|
|
15845
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
15846
|
+
logger.warn("api::consolidate failed", { error: message });
|
|
15847
|
+
return {
|
|
15848
|
+
status_code: 500,
|
|
15849
|
+
body: { error: message }
|
|
15850
|
+
};
|
|
15851
|
+
}
|
|
15891
15852
|
});
|
|
15892
15853
|
sdk.registerTrigger({
|
|
15893
15854
|
type: "http",
|
|
@@ -20592,6 +20553,15 @@ async function main() {
|
|
|
20592
20553
|
if (snapshotConfig.enabled) {
|
|
20593
20554
|
registerSnapshotFunction(sdk, kv, snapshotConfig.dir);
|
|
20594
20555
|
bootLog(`Git snapshots: ${snapshotConfig.dir} (every ${snapshotConfig.interval}s)`);
|
|
20556
|
+
const snapshotIntervalMs = snapshotConfig.interval * 1e3;
|
|
20557
|
+
setInterval(async () => {
|
|
20558
|
+
try {
|
|
20559
|
+
await sdk.trigger({
|
|
20560
|
+
function_id: "mem::snapshot-create",
|
|
20561
|
+
payload: {}
|
|
20562
|
+
});
|
|
20563
|
+
} catch {}
|
|
20564
|
+
}, snapshotIntervalMs).unref();
|
|
20595
20565
|
}
|
|
20596
20566
|
const bm25Index = getSearchIndex();
|
|
20597
20567
|
const graphWeight = parseFloat(getEnvVar("AGENTMEMORY_GRAPH_WEIGHT") || "0.3");
|
|
@@ -20742,4 +20712,4 @@ main().catch((err) => {
|
|
|
20742
20712
|
//#endregion
|
|
20743
20713
|
export {};
|
|
20744
20714
|
|
|
20745
|
-
//# sourceMappingURL=src-
|
|
20715
|
+
//# sourceMappingURL=src-Dl5_tJgD.mjs.map
|