@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.
Files changed (86) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/README.zh-TW.md +186 -0
  4. package/dist/api.d.ts +100 -0
  5. package/dist/api.js +156 -0
  6. package/dist/cognition/causal-attribution.d.ts +36 -0
  7. package/dist/cognition/causal-attribution.js +239 -0
  8. package/dist/cognition/causal-engine.d.ts +105 -0
  9. package/dist/cognition/causal-engine.js +150 -0
  10. package/dist/cognition/conflict-detector.d.ts +39 -0
  11. package/dist/cognition/conflict-detector.js +193 -0
  12. package/dist/cognition/global-working-memory.d.ts +53 -0
  13. package/dist/cognition/global-working-memory.js +211 -0
  14. package/dist/cognition/hooks-engine.d.ts +99 -0
  15. package/dist/cognition/hooks-engine.js +672 -0
  16. package/dist/cognition/ralph-core.d.ts +28 -0
  17. package/dist/cognition/ralph-core.js +104 -0
  18. package/dist/distill/concentrator-adapter.d.ts +167 -0
  19. package/dist/distill/concentrator-adapter.js +1876 -0
  20. package/dist/engine.d.ts +402 -0
  21. package/dist/engine.js +2254 -0
  22. package/dist/index.d.ts +6 -0
  23. package/dist/index.js +3 -0
  24. package/dist/lifecycle/cleanup-engine.d.ts +80 -0
  25. package/dist/lifecycle/cleanup-engine.js +162 -0
  26. package/dist/lifecycle/cleanup-state.d.ts +34 -0
  27. package/dist/lifecycle/cleanup-state.js +50 -0
  28. package/dist/lifecycle/night-consolidation.d.ts +102 -0
  29. package/dist/lifecycle/night-consolidation.js +640 -0
  30. package/dist/lifecycle/night-recovery.d.ts +40 -0
  31. package/dist/lifecycle/night-recovery.js +107 -0
  32. package/dist/paths.d.ts +17 -0
  33. package/dist/paths.js +16 -0
  34. package/dist/pipeline/capsule-bridge.d.ts +35 -0
  35. package/dist/pipeline/capsule-bridge.js +86 -0
  36. package/dist/pipeline/compact-request.d.ts +30 -0
  37. package/dist/pipeline/compact-request.js +66 -0
  38. package/dist/pipeline/inbox-watcher.d.ts +112 -0
  39. package/dist/pipeline/inbox-watcher.js +1039 -0
  40. package/dist/ports.d.ts +29 -0
  41. package/dist/ports.js +1 -0
  42. package/dist/providers/embedder-v5.d.ts +46 -0
  43. package/dist/providers/embedder-v5.js +155 -0
  44. package/dist/providers/ollama-embedding.d.ts +25 -0
  45. package/dist/providers/ollama-embedding.js +166 -0
  46. package/dist/retrieval/abstractness-judge.d.ts +14 -0
  47. package/dist/retrieval/abstractness-judge.js +87 -0
  48. package/dist/retrieval/coverage-selection.d.ts +3 -0
  49. package/dist/retrieval/coverage-selection.js +53 -0
  50. package/dist/retrieval/cross-encoder-gate.d.ts +40 -0
  51. package/dist/retrieval/cross-encoder-gate.js +239 -0
  52. package/dist/retrieval/retriever-v4.d.ts +78 -0
  53. package/dist/retrieval/retriever-v4.js +1200 -0
  54. package/dist/skills/validate.d.ts +6 -0
  55. package/dist/skills/validate.js +69 -0
  56. package/dist/storage.d.ts +19 -0
  57. package/dist/storage.js +54 -0
  58. package/dist/store/aux-table-maintenance.d.ts +5 -0
  59. package/dist/store/aux-table-maintenance.js +64 -0
  60. package/dist/store/graph-enumerator.d.ts +21 -0
  61. package/dist/store/graph-enumerator.js +185 -0
  62. package/dist/store/graph-store.d.ts +107 -0
  63. package/dist/store/graph-store.js +478 -0
  64. package/dist/store/status-manager.d.ts +44 -0
  65. package/dist/store/status-manager.js +235 -0
  66. package/dist/store/store-v4.d.ts +339 -0
  67. package/dist/store/store-v4.js +2871 -0
  68. package/dist/transcript/keyword-search.d.ts +9 -0
  69. package/dist/transcript/keyword-search.js +67 -0
  70. package/dist/transcript/rehydrate-keyword.d.ts +6 -0
  71. package/dist/transcript/rehydrate-keyword.js +29 -0
  72. package/dist/transcript/rehydrate.d.ts +33 -0
  73. package/dist/transcript/rehydrate.js +285 -0
  74. package/dist/transcript/transcript-archive.d.ts +46 -0
  75. package/dist/transcript/transcript-archive.js +516 -0
  76. package/dist/types.d.ts +409 -0
  77. package/dist/types.js +104 -0
  78. package/dist/util/bounded-map.d.ts +1 -0
  79. package/dist/util/bounded-map.js +8 -0
  80. package/dist/util/rate-limiter.d.ts +12 -0
  81. package/dist/util/rate-limiter.js +54 -0
  82. package/dist/util/session-identity.d.ts +65 -0
  83. package/dist/util/session-identity.js +227 -0
  84. package/dist/util/util-hash.d.ts +1 -0
  85. package/dist/util/util-hash.js +4 -0
  86. package/package.json +59 -0
@@ -0,0 +1,107 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ export const NIGHT_RECOVERY_THRESHOLD_MS = 24 * 60 * 60 * 1000;
3
+ export function buildNightRecoveryMetadata(args) {
4
+ const metadata = { source: args.source };
5
+ for (const [key, value] of Object.entries(args)) {
6
+ if (key === 'source')
7
+ continue;
8
+ if (value !== undefined && value !== null)
9
+ metadata[key] = value;
10
+ }
11
+ return JSON.stringify(metadata);
12
+ }
13
+ export async function shouldRunNow(args) {
14
+ if (args.isRunning) {
15
+ return {
16
+ shouldRun: false,
17
+ reason: 'already_running',
18
+ lastSuccessfulRunTs: args.lastSuccessfulRunTs,
19
+ };
20
+ }
21
+ const lastSuccessfulRunTs = args.lastSuccessfulRunTs;
22
+ if (!lastSuccessfulRunTs) {
23
+ return {
24
+ shouldRun: true,
25
+ reason: 'no_success_record',
26
+ lastSuccessfulRunTs: null,
27
+ };
28
+ }
29
+ const nowMs = args.nowMs ?? Date.now();
30
+ const thresholdMs = args.thresholdMs ?? NIGHT_RECOVERY_THRESHOLD_MS;
31
+ if (nowMs - lastSuccessfulRunTs < thresholdMs) {
32
+ return {
33
+ shouldRun: false,
34
+ reason: 'recent_run',
35
+ lastSuccessfulRunTs,
36
+ };
37
+ }
38
+ return {
39
+ shouldRun: true,
40
+ reason: 'stale_run',
41
+ lastSuccessfulRunTs,
42
+ };
43
+ }
44
+ export async function healthCheck(options) {
45
+ const now = options.now ?? Date.now;
46
+ const runIdFactory = options.runIdFactory ?? randomUUID;
47
+ if (options.isRunning()) {
48
+ const decision = await shouldRunNow({
49
+ isRunning: true,
50
+ lastSuccessfulRunTs: null,
51
+ nowMs: now(),
52
+ thresholdMs: options.thresholdMs,
53
+ });
54
+ options.recordStat({
55
+ runId: runIdFactory(),
56
+ phase: 'recovery_skipped',
57
+ ts: now(),
58
+ outcome: 'skipped',
59
+ metadata: buildNightRecoveryMetadata({
60
+ source: options.source,
61
+ reason: 'already_running',
62
+ }),
63
+ });
64
+ return decision;
65
+ }
66
+ options.setRunning?.(true);
67
+ try {
68
+ const lastSuccessfulRunTs = await options.getLastSuccessfulRunTs();
69
+ const decision = await shouldRunNow({
70
+ isRunning: false,
71
+ lastSuccessfulRunTs,
72
+ nowMs: now(),
73
+ thresholdMs: options.thresholdMs,
74
+ });
75
+ if (!decision.shouldRun) {
76
+ options.recordStat({
77
+ runId: runIdFactory(),
78
+ phase: 'recovery_skipped',
79
+ ts: now(),
80
+ outcome: 'skipped',
81
+ metadata: buildNightRecoveryMetadata({
82
+ source: options.source,
83
+ reason: 'recent_run',
84
+ lastSuccessfulRunTs,
85
+ }),
86
+ });
87
+ return decision;
88
+ }
89
+ if (options.source !== 'scheduled_timer') {
90
+ options.recordStat({
91
+ runId: runIdFactory(),
92
+ phase: 'recovery_triggered',
93
+ ts: now(),
94
+ outcome: 'triggered',
95
+ metadata: buildNightRecoveryMetadata({
96
+ source: options.source,
97
+ lastSuccessfulRunTs,
98
+ }),
99
+ });
100
+ }
101
+ await options.runNightConsolidation(options.source);
102
+ return decision;
103
+ }
104
+ finally {
105
+ options.setRunning?.(false);
106
+ }
107
+ }
@@ -0,0 +1,17 @@
1
+ export interface MemoryRiverPaths {
2
+ dataDir: string;
3
+ ramDir?: string | null;
4
+ }
5
+ export declare function resolvePaths(p: MemoryRiverPaths): {
6
+ dbDir: string;
7
+ ramDbDir: string | null;
8
+ inboxDir: string;
9
+ walFile: string;
10
+ transcriptsDir: string;
11
+ trashDir: string;
12
+ stateDir: string;
13
+ gwmStateFile: string;
14
+ consolidationLog: string;
15
+ sessionSummaryDir: string;
16
+ rerankerCacheDir: string;
17
+ };
package/dist/paths.js ADDED
@@ -0,0 +1,16 @@
1
+ import { join } from 'node:path';
2
+ export function resolvePaths(p) {
3
+ return {
4
+ dbDir: join(p.dataDir, 'lancedb'),
5
+ ramDbDir: p.ramDir ?? null,
6
+ inboxDir: join(p.dataDir, 'inbox'),
7
+ walFile: join(p.dataDir, 'wal.jsonl'),
8
+ transcriptsDir: join(p.dataDir, 'transcripts'),
9
+ trashDir: join(p.dataDir, '.trash'),
10
+ stateDir: join(p.dataDir, 'state'), // cleanup-state.json
11
+ gwmStateFile: join(p.dataDir, 'global-working-memory.json'),
12
+ consolidationLog: join(p.dataDir, 'consolidation-log.jsonl'),
13
+ sessionSummaryDir: join(p.dataDir, 'session-summaries'),
14
+ rerankerCacheDir: join(p.dataDir, '.model-cache'), // 原 ~/.cache/huggingface
15
+ };
16
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * CapsuleBridge — 膠囊統一出口 (無損通道版)
3
+ *
4
+ * 原則:
5
+ * - 所有濃縮膠囊統一經過這裡寫入 shared inbox
6
+ * - inbox 路徑由 adapter 注入
7
+ * - 寫入格式:river_capsule_{timestamp}.txt(讓 inbox-watcher 能識別並處理)
8
+ */
9
+ export interface CapsuleWriteOptions {
10
+ category?: string;
11
+ importance?: number;
12
+ metadata?: Record<string, unknown>;
13
+ /** 技能膠囊擴展欄位(inbox-watcher 讀取後入庫) */
14
+ capsuleType?: string;
15
+ skillName?: string;
16
+ triggerConditions?: string[];
17
+ executionSteps?: string[];
18
+ confidence?: number;
19
+ health?: number;
20
+ }
21
+ export declare class CapsuleBridge {
22
+ private inboxPath;
23
+ constructor(inboxPath: string);
24
+ /** 寫入濃縮膠囊到 shared inbox */
25
+ writeToInbox(text: string, opts?: CapsuleWriteOptions): Promise<string>;
26
+ /**
27
+ * 直接寫入 inbox JSON item(繞過 inbox-watcher 直接入庫的路徑)
28
+ * 用於 remember() 的高重要性記憶,直接生成完整 entry
29
+ */
30
+ writeInboxItem(text: string, opts: CapsuleWriteOptions): Promise<string>;
31
+ /** inbox 目前堆積的膠囊數量(除錯用) */
32
+ getPendingCount(): number;
33
+ /** inbox 目前堆積的 pending JSON 數量 */
34
+ getInboxItemCount(): number;
35
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * CapsuleBridge — 膠囊統一出口 (無損通道版)
3
+ *
4
+ * 原則:
5
+ * - 所有濃縮膠囊統一經過這裡寫入 shared inbox
6
+ * - inbox 路徑由 adapter 注入
7
+ * - 寫入格式:river_capsule_{timestamp}.txt(讓 inbox-watcher 能識別並處理)
8
+ */
9
+ import * as fs from 'fs';
10
+ import * as path from 'path';
11
+ import { randomUUID } from 'crypto';
12
+ export class CapsuleBridge {
13
+ inboxPath;
14
+ constructor(inboxPath) {
15
+ this.inboxPath = inboxPath;
16
+ if (!fs.existsSync(this.inboxPath)) {
17
+ fs.mkdirSync(this.inboxPath, { recursive: true, mode: 0o700 });
18
+ }
19
+ }
20
+ /** 寫入濃縮膠囊到 shared inbox */
21
+ async writeToInbox(text, opts = {}) {
22
+ if (!fs.existsSync(this.inboxPath)) {
23
+ fs.mkdirSync(this.inboxPath, { recursive: true, mode: 0o700 });
24
+ }
25
+ // 🎯 P0 修復:加隨機後綴防同毫秒並行寫互相覆蓋(對照 writeInboxItem 的做法)
26
+ const filename = `river_capsule_${Date.now()}_${randomUUID().slice(0, 8)}.txt`;
27
+ const filePath = path.join(this.inboxPath, filename);
28
+ // 🎯 核心修復:使用展開運算子 (...opts.metadata) 確保所有基因無損繼承
29
+ const capsuleMeta = {
30
+ capsuleType: opts.capsuleType ?? 'working_memory',
31
+ skillName: opts.skillName,
32
+ triggerConditions: opts.triggerConditions ?? [],
33
+ executionSteps: opts.executionSteps ?? [],
34
+ confidence: opts.confidence ?? 0,
35
+ category: opts.category ?? 'history',
36
+ importance: opts.importance ?? 0.5,
37
+ health: opts.health ?? opts.metadata?.health, // 確保 health 不被遺漏
38
+ ...opts.metadata, // 將所有客製化標籤 (如 tags, type) 完整打包
39
+ };
40
+ // 🛠️ P1-5 修復:序列化完整 capsuleMeta(含 confidence/firstTimestamp/lastTimestamp)
41
+ const metaHeader = JSON.stringify({ ...capsuleMeta, timestamp: Date.now() });
42
+ const fileContent = `<!-- CAPSULE_META:${metaHeader} -->\n${text}`;
43
+ // 🛡️ 'wx' flag:檔案已存在就拒絕覆蓋(配合隨機後綴,理論上不該撞名)
44
+ await fs.promises.writeFile(filePath, fileContent, { encoding: 'utf-8', flag: 'wx', mode: 0o600 });
45
+ const logMeta = opts.capsuleType === 'skill_capsule'
46
+ ? ` [技能膠囊:${opts.skillName ?? ''} conf=${opts.confidence ?? 0}]`
47
+ : ` [健康度:${capsuleMeta.health ?? '永久'}]`;
48
+ console.log(`[CapsuleBridge] Capsule written to inbox: ${filename} (${text.length} chars)${logMeta}`);
49
+ return filePath;
50
+ }
51
+ /**
52
+ * 直接寫入 inbox JSON item(繞過 inbox-watcher 直接入庫的路徑)
53
+ * 用於 remember() 的高重要性記憶,直接生成完整 entry
54
+ */
55
+ async writeInboxItem(text, opts) {
56
+ if (!fs.existsSync(this.inboxPath)) {
57
+ fs.mkdirSync(this.inboxPath, { recursive: true, mode: 0o700 });
58
+ }
59
+ const filename = `pending_${Date.now()}_${Math.random().toString(36).slice(2, 6)}.json`;
60
+ const filePath = path.join(this.inboxPath, filename);
61
+ // 這裡原本寫得很好,有接住 ...opts.metadata
62
+ const item = {
63
+ text,
64
+ category: opts.category ?? 'other',
65
+ importance: opts.importance ?? 0.5,
66
+ tags: opts.metadata?.tags ?? [],
67
+ health: opts.health ?? opts.metadata?.health,
68
+ ...opts.metadata,
69
+ };
70
+ await fs.promises.writeFile(filePath, JSON.stringify(item, null, 2), { encoding: 'utf-8', mode: 0o600 });
71
+ console.log(`[CapsuleBridge] Inbox item written: ${filename} (category=${item.category}, importance=${item.importance})`);
72
+ return filePath;
73
+ }
74
+ /** inbox 目前堆積的膠囊數量(除錯用) */
75
+ getPendingCount() {
76
+ if (!fs.existsSync(this.inboxPath))
77
+ return 0;
78
+ return fs.readdirSync(this.inboxPath).filter((f) => f.startsWith('river_capsule_')).length;
79
+ }
80
+ /** inbox 目前堆積的 pending JSON 數量 */
81
+ getInboxItemCount() {
82
+ if (!fs.existsSync(this.inboxPath))
83
+ return 0;
84
+ return fs.readdirSync(this.inboxPath).filter((f) => f.startsWith('pending_') && f.endsWith('.json')).length;
85
+ }
86
+ }
@@ -0,0 +1,30 @@
1
+ export declare const COMPACT_REQUEST_VERSION = 1;
2
+ export declare const COMPACT_REQUEST_FILENAME_PREFIX = "compact_request_";
3
+ export interface AsyncCompactRequest {
4
+ trackingKey: string;
5
+ sessionId?: string;
6
+ sessionKey?: string;
7
+ originalTokens: number;
8
+ compressedTokens: number;
9
+ timestamp: number;
10
+ }
11
+ export interface CompactRequestInboxItem {
12
+ type: 'compact_request';
13
+ version: 1;
14
+ requestId: string;
15
+ trackingKey: string;
16
+ sessionId?: string;
17
+ sessionKey?: string;
18
+ originalTokens: number;
19
+ compressedTokens: number;
20
+ createdAt: number;
21
+ source: 'asyncCompactAfterAssemble';
22
+ }
23
+ export declare class CompactRequestSchemaError extends Error {
24
+ constructor(message: string);
25
+ }
26
+ export declare function buildCompactRequestFilename(item: CompactRequestInboxItem): string;
27
+ export declare function isCompactRequestFilename(name: string): boolean;
28
+ export declare function validateCompactRequest(raw: unknown): asserts raw is CompactRequestInboxItem;
29
+ export declare function writeCompactRequest(inboxPath: string, item: CompactRequestInboxItem): Promise<string>;
30
+ export declare function readCompactRequest(filePath: string): Promise<CompactRequestInboxItem>;
@@ -0,0 +1,66 @@
1
+ import { promises as fs } from 'node:fs';
2
+ import * as path from 'node:path';
3
+ export const COMPACT_REQUEST_VERSION = 1;
4
+ export const COMPACT_REQUEST_FILENAME_PREFIX = 'compact_request_';
5
+ export class CompactRequestSchemaError extends Error {
6
+ constructor(message) {
7
+ super(message);
8
+ this.name = 'CompactRequestSchemaError';
9
+ }
10
+ }
11
+ export function buildCompactRequestFilename(item) {
12
+ return `${COMPACT_REQUEST_FILENAME_PREFIX}${item.createdAt}_${item.requestId}.json`;
13
+ }
14
+ export function isCompactRequestFilename(name) {
15
+ return name.startsWith(COMPACT_REQUEST_FILENAME_PREFIX) && name.endsWith('.json');
16
+ }
17
+ function isFiniteNumber(value) {
18
+ return typeof value === 'number' && Number.isFinite(value);
19
+ }
20
+ function assertNonEmptyString(value, field) {
21
+ if (typeof value !== 'string' || value.trim().length === 0) {
22
+ throw new CompactRequestSchemaError(`${field} must be a non-empty string`);
23
+ }
24
+ }
25
+ export function validateCompactRequest(raw) {
26
+ if (!raw || typeof raw !== 'object') {
27
+ throw new CompactRequestSchemaError('compact request must be an object');
28
+ }
29
+ const item = raw;
30
+ if (item.type !== 'compact_request')
31
+ throw new CompactRequestSchemaError('type must be compact_request');
32
+ if (item.version !== COMPACT_REQUEST_VERSION)
33
+ throw new CompactRequestSchemaError('version must be 1');
34
+ assertNonEmptyString(item.requestId, 'requestId');
35
+ assertNonEmptyString(item.trackingKey, 'trackingKey');
36
+ if (!isFiniteNumber(item.originalTokens))
37
+ throw new CompactRequestSchemaError('originalTokens must be a finite number');
38
+ if (!isFiniteNumber(item.compressedTokens))
39
+ throw new CompactRequestSchemaError('compressedTokens must be a finite number');
40
+ if (!isFiniteNumber(item.createdAt))
41
+ throw new CompactRequestSchemaError('createdAt must be a finite number');
42
+ if (item.source !== 'asyncCompactAfterAssemble') {
43
+ throw new CompactRequestSchemaError('source must be asyncCompactAfterAssemble');
44
+ }
45
+ if (item.sessionId !== undefined && typeof item.sessionId !== 'string') {
46
+ throw new CompactRequestSchemaError('sessionId must be a string when present');
47
+ }
48
+ if (item.sessionKey !== undefined && typeof item.sessionKey !== 'string') {
49
+ throw new CompactRequestSchemaError('sessionKey must be a string when present');
50
+ }
51
+ }
52
+ export async function writeCompactRequest(inboxPath, item) {
53
+ validateCompactRequest(item);
54
+ await fs.mkdir(inboxPath, { recursive: true });
55
+ const filename = buildCompactRequestFilename(item);
56
+ const finalPath = path.join(inboxPath, filename);
57
+ const tmpPath = path.join(inboxPath, `.${item.requestId}.tmp`);
58
+ await fs.writeFile(tmpPath, JSON.stringify(item, null, 2), 'utf-8');
59
+ await fs.rename(tmpPath, finalPath);
60
+ return finalPath;
61
+ }
62
+ export async function readCompactRequest(filePath) {
63
+ const raw = JSON.parse(await fs.readFile(filePath, 'utf-8'));
64
+ validateCompactRequest(raw);
65
+ return raw;
66
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * InboxWatcher - 記憶 inbox 觀察者 + River Capsule 處理器
3
+ * memory-river
4
+ * * 改造要點:
5
+ * - inbox 路徑從構造函數參數傳入(不再 hardcode)
6
+ * - import 改為來自同目錄的本地模組
7
+ * - 保留 writeInbox() static 方法給 CapsuleBridge 呼叫
8
+ * - processRiverCapsule() 方法處理 river_capsule_*.txt 檔
9
+ */
10
+ import { MemoryStore } from "../store/store-v4.js";
11
+ import { StatusManager } from "../store/status-manager.js";
12
+ import { Embedder } from "../providers/embedder-v5.js";
13
+ import { CausalEngine } from "../cognition/causal-engine.js";
14
+ import { HooksEngine } from "../cognition/hooks-engine.js";
15
+ import { ConflictDetector } from "../cognition/conflict-detector.js";
16
+ import { GraphStore } from "../store/graph-store.js";
17
+ import type { LlmClient } from "../ports.js";
18
+ import { type AsyncCompactRequest } from "./compact-request.js";
19
+ export declare class InboxWatcher {
20
+ private store;
21
+ private embedder;
22
+ private causalEngine;
23
+ private hooksEngine;
24
+ private graphStore;
25
+ private llm;
26
+ private inboxPath;
27
+ private pollIntervalMs;
28
+ private conflictDetector;
29
+ private statusManager;
30
+ private compactRequestProcessor;
31
+ private static readonly RIVER_CAPSULE_FAILURE_THRESHOLD;
32
+ private static readonly RIVER_CAPSULE_FAILURE_LRU_MAX;
33
+ private intervalId;
34
+ private isProcessing;
35
+ private processingStartedAt;
36
+ private started;
37
+ private fatalErrorCount;
38
+ private riverCapsuleFailureCounts;
39
+ private parentIdLocks;
40
+ private withParentLock;
41
+ private parseMetadata;
42
+ constructor(store: MemoryStore, embedder: Embedder, causalEngine: CausalEngine, hooksEngine: HooksEngine | null, graphStore: GraphStore | null, llm: LlmClient, inboxPath: string, pollIntervalMs: number | undefined, conflictDetector: ConflictDetector | undefined, statusManager: StatusManager, compactRequestProcessor: (req: AsyncCompactRequest) => Promise<void>);
43
+ setDependencies(hooksEngine: HooksEngine, graphStore: GraphStore): void;
44
+ private countPendingFiles;
45
+ private runProcessInbox;
46
+ private recordFatalError;
47
+ private setRiverCapsuleFailureCount;
48
+ private classifyRiverCapsuleReason;
49
+ private recordRiverCapsuleStat;
50
+ private moveRiverCapsuleToError;
51
+ /**
52
+ * 從 river capsule meta 推導 stat / failure counter 用的 key。
53
+ *
54
+ * 與 sessionIdentity 主鏈不同:river capsule 的 metadata 可能巢狀
55
+ * (capsuleMeta.metadata.sessionKey),且最後保底是 filename,不是 'global'。
56
+ * 因此先把巢狀欄位攤平成標準 payload,再交給 sessionIdentity 解析;
57
+ * 若 isFallback(payload 完全沒有 session 身分)才退到 filename。
58
+ */
59
+ private getRiverCapsuleSessionKey;
60
+ start(): void;
61
+ stop(): void;
62
+ private processCompactRequest;
63
+ private executeCompactRequest;
64
+ private processInbox;
65
+ processRiverCapsule(filePath: string): Promise<{
66
+ ok: boolean;
67
+ reason?: string;
68
+ sessionKey?: string;
69
+ }>;
70
+ private processFile;
71
+ /**
72
+ * _processMemoryEntry — 濃縮膠囊寫入記憶庫的核心 pipeline
73
+ * 不處理檔案狀態(由 processFile 統一管理 rename/delete)
74
+ */
75
+ private _processMemoryEntry;
76
+ static writeInbox(inboxPath: string, item: {
77
+ text: string;
78
+ category: string;
79
+ importance?: number;
80
+ parentId?: string;
81
+ capsuleType?: string;
82
+ skillName?: string;
83
+ triggerConditions?: string[];
84
+ executionSteps?: string[];
85
+ confidence?: number;
86
+ metadata?: Record<string, unknown>;
87
+ tool_result?: Record<string, unknown> | null;
88
+ }): Promise<string>;
89
+ /**
90
+ * 從記憶文字萃取技能名稱(簡單關鍵詞法,fallback)
91
+ */
92
+ private extractSkillNameFromText;
93
+ /**
94
+ * 從記憶文字抽取觸發關鍵詞(簡單枚舉法)
95
+ */
96
+ private extractTriggersFromText;
97
+ /**
98
+ * extractSlot — LLM 結構化抽取
99
+ * 輸入文字 → 判斷是否可結構化 → 輸出 slotKey / slotValue / confidence / extractionDomain
100
+ *
101
+ * confidence 等級:
102
+ * >= 0.8:高可信,建立 Slot,自動 supersedes 檢查
103
+ * 0.5–0.8:中可信,寫入待審核池(Night Consolidation 處理)
104
+ * < 0.5:低可信,純 free-text,不建 Slot
105
+ */
106
+ private extractSlot;
107
+ /**
108
+ * checkSupersedes — 查詢同 slotKey 的所有 active 舊版本
109
+ * @returns 被取代的舊 entry id 清單
110
+ */
111
+ private checkSupersedes;
112
+ }