@memory-river/core 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +222 -0
- package/README.zh-TW.md +186 -0
- package/dist/api.d.ts +100 -0
- package/dist/api.js +156 -0
- package/dist/cognition/causal-attribution.d.ts +36 -0
- package/dist/cognition/causal-attribution.js +239 -0
- package/dist/cognition/causal-engine.d.ts +105 -0
- package/dist/cognition/causal-engine.js +150 -0
- package/dist/cognition/conflict-detector.d.ts +39 -0
- package/dist/cognition/conflict-detector.js +193 -0
- package/dist/cognition/global-working-memory.d.ts +53 -0
- package/dist/cognition/global-working-memory.js +211 -0
- package/dist/cognition/hooks-engine.d.ts +99 -0
- package/dist/cognition/hooks-engine.js +672 -0
- package/dist/cognition/ralph-core.d.ts +28 -0
- package/dist/cognition/ralph-core.js +104 -0
- package/dist/distill/concentrator-adapter.d.ts +167 -0
- package/dist/distill/concentrator-adapter.js +1876 -0
- package/dist/engine.d.ts +402 -0
- package/dist/engine.js +2254 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +3 -0
- package/dist/lifecycle/cleanup-engine.d.ts +80 -0
- package/dist/lifecycle/cleanup-engine.js +162 -0
- package/dist/lifecycle/cleanup-state.d.ts +34 -0
- package/dist/lifecycle/cleanup-state.js +50 -0
- package/dist/lifecycle/night-consolidation.d.ts +102 -0
- package/dist/lifecycle/night-consolidation.js +640 -0
- package/dist/lifecycle/night-recovery.d.ts +40 -0
- package/dist/lifecycle/night-recovery.js +107 -0
- package/dist/paths.d.ts +17 -0
- package/dist/paths.js +16 -0
- package/dist/pipeline/capsule-bridge.d.ts +35 -0
- package/dist/pipeline/capsule-bridge.js +86 -0
- package/dist/pipeline/compact-request.d.ts +30 -0
- package/dist/pipeline/compact-request.js +66 -0
- package/dist/pipeline/inbox-watcher.d.ts +112 -0
- package/dist/pipeline/inbox-watcher.js +1039 -0
- package/dist/ports.d.ts +29 -0
- package/dist/ports.js +1 -0
- package/dist/providers/embedder-v5.d.ts +46 -0
- package/dist/providers/embedder-v5.js +155 -0
- package/dist/providers/ollama-embedding.d.ts +25 -0
- package/dist/providers/ollama-embedding.js +166 -0
- package/dist/retrieval/abstractness-judge.d.ts +14 -0
- package/dist/retrieval/abstractness-judge.js +87 -0
- package/dist/retrieval/coverage-selection.d.ts +3 -0
- package/dist/retrieval/coverage-selection.js +53 -0
- package/dist/retrieval/cross-encoder-gate.d.ts +40 -0
- package/dist/retrieval/cross-encoder-gate.js +239 -0
- package/dist/retrieval/retriever-v4.d.ts +78 -0
- package/dist/retrieval/retriever-v4.js +1200 -0
- package/dist/skills/validate.d.ts +6 -0
- package/dist/skills/validate.js +69 -0
- package/dist/storage.d.ts +19 -0
- package/dist/storage.js +54 -0
- package/dist/store/aux-table-maintenance.d.ts +5 -0
- package/dist/store/aux-table-maintenance.js +64 -0
- package/dist/store/graph-enumerator.d.ts +21 -0
- package/dist/store/graph-enumerator.js +185 -0
- package/dist/store/graph-store.d.ts +107 -0
- package/dist/store/graph-store.js +478 -0
- package/dist/store/status-manager.d.ts +44 -0
- package/dist/store/status-manager.js +235 -0
- package/dist/store/store-v4.d.ts +339 -0
- package/dist/store/store-v4.js +2871 -0
- package/dist/transcript/keyword-search.d.ts +9 -0
- package/dist/transcript/keyword-search.js +67 -0
- package/dist/transcript/rehydrate-keyword.d.ts +6 -0
- package/dist/transcript/rehydrate-keyword.js +29 -0
- package/dist/transcript/rehydrate.d.ts +33 -0
- package/dist/transcript/rehydrate.js +285 -0
- package/dist/transcript/transcript-archive.d.ts +46 -0
- package/dist/transcript/transcript-archive.js +516 -0
- package/dist/types.d.ts +409 -0
- package/dist/types.js +104 -0
- package/dist/util/bounded-map.d.ts +1 -0
- package/dist/util/bounded-map.js +8 -0
- package/dist/util/rate-limiter.d.ts +12 -0
- package/dist/util/rate-limiter.js +54 -0
- package/dist/util/session-identity.d.ts +65 -0
- package/dist/util/session-identity.js +227 -0
- package/dist/util/util-hash.d.ts +1 -0
- package/dist/util/util-hash.js +4 -0
- package/package.json +59 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* session-identity.ts
|
|
3
|
+
*
|
|
4
|
+
* 統一的 session 身分解析模組(P0-2)。
|
|
5
|
+
*
|
|
6
|
+
* 替代既有 resolveTrackingSessionKey / extractSessionIdentity 雙鏈,把
|
|
7
|
+
* 「從 payload 抽 session 身分」與「決定對外 canonicalKey」收斂到同一條路徑。
|
|
8
|
+
*
|
|
9
|
+
* - 對外只暴露 canonicalKey;所有 in-memory state map / Set 都應該以它為 key。
|
|
10
|
+
* - canonicalKey 優先序:sessionKey -> session.key -> session.id -> sessionId
|
|
11
|
+
* (與磁碟序列化最重的 transcript archive 既有 sessionKey 命名一致)。
|
|
12
|
+
* - 原始 sessionKey / sessionId 仍保留在 record 裡,給磁碟讀寫拼檔名用。
|
|
13
|
+
*
|
|
14
|
+
* 純函式(除 console / fs 觀測點),不可在這裡碰任何 state map。
|
|
15
|
+
*/
|
|
16
|
+
import * as fs from 'fs';
|
|
17
|
+
import * as path from 'node:path';
|
|
18
|
+
export const GLOBAL_FALLBACK_KEY = 'global';
|
|
19
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
20
|
+
* Fallback warning 去重(LRU 1000 unique payload signature)
|
|
21
|
+
*
|
|
22
|
+
* Phase 4-5:log 走 dedupe 避免 spam;observer 每次 fallback 都觸發,
|
|
23
|
+
* 上層可拿來寫 concentrator_stats 等持久觀測。
|
|
24
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
25
|
+
const FALLBACK_LOG_MAX = 1000;
|
|
26
|
+
const fallbackSignatureSeen = new Set();
|
|
27
|
+
let fallbackObserver = null;
|
|
28
|
+
/** 註冊全域 fallback observer;傳 null 取消。 */
|
|
29
|
+
export function setFallbackObserver(fn) {
|
|
30
|
+
fallbackObserver = fn;
|
|
31
|
+
}
|
|
32
|
+
function payloadSignature(payload) {
|
|
33
|
+
if (!payload || typeof payload !== 'object')
|
|
34
|
+
return `<${typeof payload}>`;
|
|
35
|
+
const keys = Object.keys(payload).sort();
|
|
36
|
+
return keys.length === 0 ? '<empty-object>' : keys.join(',');
|
|
37
|
+
}
|
|
38
|
+
function notifyFallback(payload, ctx) {
|
|
39
|
+
const sig = payloadSignature(payload);
|
|
40
|
+
// observer:每次 fallback 都觸發(上層拿來寫 stats)
|
|
41
|
+
if (fallbackObserver) {
|
|
42
|
+
try {
|
|
43
|
+
fallbackObserver({ signature: sig, ctx });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
// observer 錯誤不能影響本流程
|
|
47
|
+
console.warn('[sessionIdentity] fallbackObserver threw:', err);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// log:用 LRU dedupe 避免 spam,只 log payload keys 不 log value
|
|
51
|
+
if (fallbackSignatureSeen.has(sig))
|
|
52
|
+
return;
|
|
53
|
+
if (fallbackSignatureSeen.size >= FALLBACK_LOG_MAX) {
|
|
54
|
+
const oldest = fallbackSignatureSeen.values().next().value;
|
|
55
|
+
if (oldest !== undefined)
|
|
56
|
+
fallbackSignatureSeen.delete(oldest);
|
|
57
|
+
}
|
|
58
|
+
fallbackSignatureSeen.add(sig);
|
|
59
|
+
console.warn(`[sessionIdentity] global fallback ctx=${ctx} payloadKeys=[${sig}]`);
|
|
60
|
+
}
|
|
61
|
+
function pickString(value) {
|
|
62
|
+
if (typeof value !== 'string')
|
|
63
|
+
return null;
|
|
64
|
+
const normalized = value.trim();
|
|
65
|
+
return normalized.length > 0 ? normalized : null;
|
|
66
|
+
}
|
|
67
|
+
function extractRawFields(payload) {
|
|
68
|
+
if (!payload || typeof payload !== 'object') {
|
|
69
|
+
return { sessionKey: null, sessionDotKey: null, sessionDotId: null, sessionId: null };
|
|
70
|
+
}
|
|
71
|
+
const obj = payload;
|
|
72
|
+
const session = (obj.session && typeof obj.session === 'object')
|
|
73
|
+
? obj.session
|
|
74
|
+
: null;
|
|
75
|
+
return {
|
|
76
|
+
sessionKey: pickString(obj.sessionKey),
|
|
77
|
+
sessionDotKey: session ? pickString(session.key) : null,
|
|
78
|
+
sessionDotId: session ? pickString(session.id) : null,
|
|
79
|
+
sessionId: pickString(obj.sessionId),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function chooseCanonical(raw) {
|
|
83
|
+
if (raw.sessionKey)
|
|
84
|
+
return { canonicalKey: raw.sessionKey, source: 'sessionKey', isFallback: false };
|
|
85
|
+
if (raw.sessionDotKey)
|
|
86
|
+
return { canonicalKey: raw.sessionDotKey, source: 'session.key', isFallback: false };
|
|
87
|
+
if (raw.sessionDotId)
|
|
88
|
+
return { canonicalKey: raw.sessionDotId, source: 'session.id', isFallback: false };
|
|
89
|
+
if (raw.sessionId)
|
|
90
|
+
return { canonicalKey: raw.sessionId, source: 'sessionId', isFallback: false };
|
|
91
|
+
return { canonicalKey: GLOBAL_FALLBACK_KEY, source: 'global', isFallback: true };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 從單一 payload 解析 sessionIdentity。
|
|
95
|
+
* 同時拿 sessionKey 與 sessionId 兩個原始欄位(如果存在),以便磁碟讀寫拼檔名。
|
|
96
|
+
*/
|
|
97
|
+
export function resolveSessionIdentity(payload) {
|
|
98
|
+
const raw = extractRawFields(payload);
|
|
99
|
+
const { canonicalKey, source, isFallback } = chooseCanonical(raw);
|
|
100
|
+
if (isFallback) {
|
|
101
|
+
notifyFallback(payload, 'resolveSessionIdentity');
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
canonicalKey,
|
|
105
|
+
sessionKey: raw.sessionKey ?? raw.sessionDotKey,
|
|
106
|
+
sessionId: raw.sessionId ?? raw.sessionDotId,
|
|
107
|
+
source,
|
|
108
|
+
isFallback,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 從 variadic args 解析(assemble、register hook 場景)。
|
|
113
|
+
* 走訪每個 arg,取第一個能解出非 fallback identity 的;若全部 fallback,回傳 global。
|
|
114
|
+
*
|
|
115
|
+
* 與 resolveTrackingSessionKey 的 variadic 行為等價,但同步把 sessionKey/sessionId 一起帶出。
|
|
116
|
+
*/
|
|
117
|
+
export function resolveSessionIdentityFromArgs(...args) {
|
|
118
|
+
// 第一輪:找第一個能解出 canonicalKey 的 arg;同時把所有 args 的 sessionKey/sessionId 合併
|
|
119
|
+
let chosen = null;
|
|
120
|
+
let mergedSessionKey = null;
|
|
121
|
+
let mergedSessionId = null;
|
|
122
|
+
for (const arg of args) {
|
|
123
|
+
const raw = extractRawFields(arg);
|
|
124
|
+
const skHere = raw.sessionKey ?? raw.sessionDotKey;
|
|
125
|
+
const sidHere = raw.sessionId ?? raw.sessionDotId;
|
|
126
|
+
if (!mergedSessionKey && skHere)
|
|
127
|
+
mergedSessionKey = skHere;
|
|
128
|
+
if (!mergedSessionId && sidHere)
|
|
129
|
+
mergedSessionId = sidHere;
|
|
130
|
+
if (chosen)
|
|
131
|
+
continue;
|
|
132
|
+
const { canonicalKey, source, isFallback } = chooseCanonical(raw);
|
|
133
|
+
if (!isFallback) {
|
|
134
|
+
chosen = {
|
|
135
|
+
canonicalKey,
|
|
136
|
+
sessionKey: skHere,
|
|
137
|
+
sessionId: sidHere,
|
|
138
|
+
source,
|
|
139
|
+
isFallback: false,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (chosen) {
|
|
144
|
+
// merged 結果可能比單一 arg 多帶一個原始欄位,補進 record 方便下游磁碟讀寫
|
|
145
|
+
if (!chosen.sessionKey && mergedSessionKey)
|
|
146
|
+
chosen.sessionKey = mergedSessionKey;
|
|
147
|
+
if (!chosen.sessionId && mergedSessionId)
|
|
148
|
+
chosen.sessionId = mergedSessionId;
|
|
149
|
+
return chosen;
|
|
150
|
+
}
|
|
151
|
+
// 全 fallback → 用第一個 arg 的 signature 做 dedupe,避免每個 arg 都 spam
|
|
152
|
+
notifyFallback(args[0] ?? null, 'resolveSessionIdentityFromArgs');
|
|
153
|
+
return {
|
|
154
|
+
canonicalKey: GLOBAL_FALLBACK_KEY,
|
|
155
|
+
sessionKey: mergedSessionKey,
|
|
156
|
+
sessionId: mergedSessionId,
|
|
157
|
+
source: 'global',
|
|
158
|
+
isFallback: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 已知 canonicalKey 反查 SessionIdentity。
|
|
163
|
+
* 給 cleanup / scheduler / inbox-watcher 等沒有原始 payload 的場景用。
|
|
164
|
+
*
|
|
165
|
+
* source 標 'fallback' 是為了與 'global' 區分:'global' 表示 payload 不帶任何 session
|
|
166
|
+
* 身分,'fallback' 表示我們已經有 canonicalKey 但沒走過 payload resolve 流程。
|
|
167
|
+
*/
|
|
168
|
+
export function makeSessionIdentity(canonicalKey, hints) {
|
|
169
|
+
return {
|
|
170
|
+
canonicalKey,
|
|
171
|
+
sessionKey: pickString(hints?.sessionKey),
|
|
172
|
+
sessionId: pickString(hints?.sessionId),
|
|
173
|
+
source: 'fallback',
|
|
174
|
+
isFallback: canonicalKey === GLOBAL_FALLBACK_KEY,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
178
|
+
* guess-based fallback(Discord DM / memory_rehydrate without sessionKey)
|
|
179
|
+
*
|
|
180
|
+
* 這條路完全不看 live payload,只看 transcripts 目錄中 mtime 最新的檔名。
|
|
181
|
+
* 保留 mtime 邏輯(與既有 guessCurrentSessionKey 同),但每次呼叫 log 一條 info,
|
|
182
|
+
* 不再藏在 utility 角落。
|
|
183
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
184
|
+
/**
|
|
185
|
+
* 從磁碟猜當前 session(mtime 最新的 transcript .jsonl)。
|
|
186
|
+
* 找不到回 null。每次呼叫 log 一條 info,方便觀測有多少流量走這條 fallback。
|
|
187
|
+
*/
|
|
188
|
+
export function resolveSessionIdentityByGuess(transcriptsDir, ctx = 'unknown') {
|
|
189
|
+
try {
|
|
190
|
+
const dir = transcriptsDir;
|
|
191
|
+
if (!fs.existsSync(dir)) {
|
|
192
|
+
console.info(`[sessionIdentity] guess miss (no transcript dir) ctx=${ctx}`);
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
const files = fs.readdirSync(dir)
|
|
196
|
+
.filter((f) => f.endsWith('.jsonl') && !f.endsWith('.idx'))
|
|
197
|
+
.map((f) => ({
|
|
198
|
+
name: f.replace('.jsonl', ''),
|
|
199
|
+
mtime: fs.statSync(path.join(dir, f)).mtimeMs,
|
|
200
|
+
}))
|
|
201
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
202
|
+
const top = files[0]?.name;
|
|
203
|
+
if (!top) {
|
|
204
|
+
console.info(`[sessionIdentity] guess miss (no transcripts) ctx=${ctx}`);
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
console.info(`[sessionIdentity] guess hit ctx=${ctx} canonicalKey=${top}`);
|
|
208
|
+
return {
|
|
209
|
+
canonicalKey: top,
|
|
210
|
+
sessionKey: top, // transcript 檔名等同 sessionKey
|
|
211
|
+
sessionId: null,
|
|
212
|
+
source: 'guess',
|
|
213
|
+
isFallback: false,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
console.warn(`[sessionIdentity] guess failed ctx=${ctx}:`, err);
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/* ──────────────────────────────────────────────────────────────────────
|
|
222
|
+
* Test seam(可選,目前 codebase 無 test infra,僅留 hook)
|
|
223
|
+
* ──────────────────────────────────────────────────────────────────── */
|
|
224
|
+
/** 重置 fallback dedupe 狀態,僅供測試使用。 */
|
|
225
|
+
export function __resetFallbackDedupeForTests() {
|
|
226
|
+
fallbackSignatureSeen.clear();
|
|
227
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hashQuery(query: string): string;
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memory-river/core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Framework-neutral long-term memory engine for Node.js agents.",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/Hsi431/memory-river.git",
|
|
10
|
+
"directory": "packages/core"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/Hsi431/memory-river#readme",
|
|
13
|
+
"bugs": "https://github.com/Hsi431/memory-river/issues",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"memory",
|
|
16
|
+
"agent",
|
|
17
|
+
"llm",
|
|
18
|
+
"long-term-memory",
|
|
19
|
+
"lancedb"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./*": {
|
|
39
|
+
"types": "./dist/*.d.ts",
|
|
40
|
+
"import": "./dist/*.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc -p tsconfig.json",
|
|
45
|
+
"clean": "rm -rf dist",
|
|
46
|
+
"prepublishOnly": "npm run build",
|
|
47
|
+
"pretest": "npm run build",
|
|
48
|
+
"test": "node --test tests/*.test.mjs tests/*.test.ts"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"@lancedb/lancedb": "^0.14.0",
|
|
52
|
+
"apache-arrow": "^18.1.0"
|
|
53
|
+
},
|
|
54
|
+
"optionalDependencies": {
|
|
55
|
+
"@xenova/transformers": "^2.17.2",
|
|
56
|
+
"onnxruntime-node": "1.14.0",
|
|
57
|
+
"nodejieba": "^3.5.7"
|
|
58
|
+
}
|
|
59
|
+
}
|