@getplumb/core 0.4.0 → 0.4.1

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 (41) hide show
  1. package/package.json +1 -1
  2. package/dist/extraction-queue.d.ts +0 -72
  3. package/dist/extraction-queue.d.ts.map +0 -1
  4. package/dist/extraction-queue.js +0 -101
  5. package/dist/extraction-queue.js.map +0 -1
  6. package/dist/extractor.d.ts +0 -22
  7. package/dist/extractor.d.ts.map +0 -1
  8. package/dist/extractor.js +0 -188
  9. package/dist/extractor.js.map +0 -1
  10. package/dist/extractor.test.d.ts +0 -2
  11. package/dist/extractor.test.d.ts.map +0 -1
  12. package/dist/extractor.test.js +0 -158
  13. package/dist/extractor.test.js.map +0 -1
  14. package/dist/fact-search.d.ts +0 -32
  15. package/dist/fact-search.d.ts.map +0 -1
  16. package/dist/fact-search.js +0 -174
  17. package/dist/fact-search.js.map +0 -1
  18. package/dist/fact-search.test.d.ts +0 -12
  19. package/dist/fact-search.test.d.ts.map +0 -1
  20. package/dist/fact-search.test.js +0 -117
  21. package/dist/fact-search.test.js.map +0 -1
  22. package/dist/llm-client.d.ts +0 -59
  23. package/dist/llm-client.d.ts.map +0 -1
  24. package/dist/llm-client.js +0 -227
  25. package/dist/llm-client.js.map +0 -1
  26. package/dist/local-store.test.d.ts +0 -2
  27. package/dist/local-store.test.d.ts.map +0 -1
  28. package/dist/local-store.test.js +0 -146
  29. package/dist/local-store.test.js.map +0 -1
  30. package/dist/raw-log-search.test.d.ts +0 -12
  31. package/dist/raw-log-search.test.d.ts.map +0 -1
  32. package/dist/raw-log-search.test.js +0 -124
  33. package/dist/raw-log-search.test.js.map +0 -1
  34. package/dist/read-path.test.d.ts +0 -15
  35. package/dist/read-path.test.d.ts.map +0 -1
  36. package/dist/read-path.test.js +0 -393
  37. package/dist/read-path.test.js.map +0 -1
  38. package/dist/scorer.test.d.ts +0 -10
  39. package/dist/scorer.test.d.ts.map +0 -1
  40. package/dist/scorer.test.js +0 -169
  41. package/dist/scorer.test.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getplumb/core",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Plumb memory engine — storage abstraction, types, and local SQLite driver",
5
5
  "license": "MIT",
6
6
  "author": "Clay Waters <hello@plumb.run>",
@@ -1,72 +0,0 @@
1
- import type { MessageExchange, Fact } from './types.js';
2
- /**
3
- * Extraction function signature expected by ExtractionQueue.
4
- * Takes an exchange, userId, and sourceChunkId, returns extracted facts.
5
- * LocalStore binds extractFacts with its own store + llmConfig.
6
- * T-079: Added sourceChunkId to link extracted facts back to raw_log chunk.
7
- */
8
- export type ExtractFn = (exchange: MessageExchange, userId: string, sourceChunkId: string) => Promise<Fact[]>;
9
- export interface ExtractionQueueOptions {
10
- /** Drain interval in milliseconds. Defaults to PLUMB_EXTRACT_INTERVAL_MS env var or 300000 (5 min). */
11
- intervalMs?: number;
12
- /** Max queue size before early flush. Defaults to PLUMB_EXTRACT_BATCH_SIZE env var or 10. */
13
- batchSize?: number;
14
- /**
15
- * Delay between individual extraction calls within a flush batch (ms).
16
- * Defaults to PLUMB_EXTRACT_ITEM_DELAY_MS env var or 6500ms.
17
- * Set to pace requests within LLM provider rate limits (e.g. Gemini free tier: 10 RPM = 1 req/6s).
18
- */
19
- itemDelayMs?: number;
20
- }
21
- /**
22
- * ExtractionQueue — batched fact extraction queue.
23
- *
24
- * Replaces the immediate fire-and-forget extractFacts() call inside ingest() with
25
- * a deferred queue. Raw exchanges are buffered in memory; a background drain loop
26
- * flushes the queue periodically (default 5 min) or when batch size is reached (default 10).
27
- *
28
- * This is a pure cost optimization: one extractFacts() call per exchange, just deferred.
29
- *
30
- * Usage:
31
- * const queue = new ExtractionQueue(extractFn, { intervalMs: 300_000, batchSize: 10 });
32
- * queue.start();
33
- * queue.enqueue(exchange, userId);
34
- * // ... later
35
- * await queue.stop(); // flushes remaining items
36
- *
37
- * @see T-071
38
- */
39
- export declare class ExtractionQueue {
40
- private readonly extractFn;
41
- private queue;
42
- private timer;
43
- private readonly intervalMs;
44
- private readonly batchSize;
45
- private readonly itemDelayMs;
46
- private flushing;
47
- constructor(extractFn: ExtractFn, opts?: ExtractionQueueOptions);
48
- /**
49
- * Enqueue an exchange for fact extraction.
50
- * Triggers early flush if batch size threshold is reached.
51
- * T-079: Added sourceChunkId parameter to link extracted facts back to raw_log chunk.
52
- */
53
- enqueue(exchange: MessageExchange, userId: string, sourceChunkId: string): void;
54
- /**
55
- * Start the background drain loop.
56
- * Call this once after construction (e.g., in plugin activate()).
57
- */
58
- start(): void;
59
- /**
60
- * Stop the background drain loop and flush remaining items.
61
- * Call this before shutdown (e.g., in plugin session_end or process exit).
62
- */
63
- stop(): Promise<void>;
64
- /**
65
- * Flush the queue immediately: drain all pending items and call extractFn for each.
66
- * Uses Promise.allSettled() so one failed extraction doesn't drop others.
67
- * Safe to call concurrently — only one flush runs at a time.
68
- * T-079: Pass sourceChunkId to extractFn for processing state machine.
69
- */
70
- flush(): Promise<void>;
71
- }
72
- //# sourceMappingURL=extraction-queue.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extraction-queue.d.ts","sourceRoot":"","sources":["../src/extraction-queue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,QAAQ,EAAE,eAAe,EACzB,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,KAClB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;AAQrB,MAAM,WAAW,sBAAsB;IACrC,uGAAuG;IACvG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,eAAe;IASxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAR5B,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAS;gBAGN,SAAS,EAAE,SAAS,EACrC,IAAI,CAAC,EAAE,sBAAsB;IAO/B;;;;OAIG;IACH,OAAO,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,IAAI;IAO/E;;;OAGG;IACH,KAAK,IAAI,IAAI;IAKb;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ3B;;;;;OAKG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA4B7B"}
@@ -1,101 +0,0 @@
1
- /**
2
- * ExtractionQueue — batched fact extraction queue.
3
- *
4
- * Replaces the immediate fire-and-forget extractFacts() call inside ingest() with
5
- * a deferred queue. Raw exchanges are buffered in memory; a background drain loop
6
- * flushes the queue periodically (default 5 min) or when batch size is reached (default 10).
7
- *
8
- * This is a pure cost optimization: one extractFacts() call per exchange, just deferred.
9
- *
10
- * Usage:
11
- * const queue = new ExtractionQueue(extractFn, { intervalMs: 300_000, batchSize: 10 });
12
- * queue.start();
13
- * queue.enqueue(exchange, userId);
14
- * // ... later
15
- * await queue.stop(); // flushes remaining items
16
- *
17
- * @see T-071
18
- */
19
- export class ExtractionQueue {
20
- extractFn;
21
- queue = [];
22
- timer = null;
23
- intervalMs;
24
- batchSize;
25
- itemDelayMs;
26
- flushing = false; // Prevent concurrent flush() calls
27
- constructor(extractFn, opts) {
28
- this.extractFn = extractFn;
29
- this.intervalMs = opts?.intervalMs ?? Number(process.env.PLUMB_EXTRACT_INTERVAL_MS ?? 300_000);
30
- this.batchSize = opts?.batchSize ?? Number(process.env.PLUMB_EXTRACT_BATCH_SIZE ?? 10);
31
- this.itemDelayMs = opts?.itemDelayMs ?? Number(process.env.PLUMB_EXTRACT_ITEM_DELAY_MS ?? 6_500);
32
- }
33
- /**
34
- * Enqueue an exchange for fact extraction.
35
- * Triggers early flush if batch size threshold is reached.
36
- * T-079: Added sourceChunkId parameter to link extracted facts back to raw_log chunk.
37
- */
38
- enqueue(exchange, userId, sourceChunkId) {
39
- this.queue.push({ exchange, userId, sourceChunkId });
40
- if (this.queue.length >= this.batchSize) {
41
- void this.flush();
42
- }
43
- }
44
- /**
45
- * Start the background drain loop.
46
- * Call this once after construction (e.g., in plugin activate()).
47
- */
48
- start() {
49
- if (this.timer !== null)
50
- return; // Already started
51
- this.timer = setInterval(() => void this.flush(), this.intervalMs);
52
- }
53
- /**
54
- * Stop the background drain loop and flush remaining items.
55
- * Call this before shutdown (e.g., in plugin session_end or process exit).
56
- */
57
- async stop() {
58
- if (this.timer !== null) {
59
- clearInterval(this.timer);
60
- this.timer = null;
61
- }
62
- await this.flush();
63
- }
64
- /**
65
- * Flush the queue immediately: drain all pending items and call extractFn for each.
66
- * Uses Promise.allSettled() so one failed extraction doesn't drop others.
67
- * Safe to call concurrently — only one flush runs at a time.
68
- * T-079: Pass sourceChunkId to extractFn for processing state machine.
69
- */
70
- async flush() {
71
- // Prevent concurrent flush() calls
72
- if (this.flushing)
73
- return;
74
- this.flushing = true;
75
- try {
76
- // Snapshot the queue and clear it atomically
77
- const batch = this.queue.splice(0);
78
- if (batch.length === 0)
79
- return;
80
- // Extract facts sequentially with per-item delay to respect LLM provider rate limits.
81
- // (Gemini free tier: 10 RPM → 1 req/6s; default itemDelayMs=6500 gives safe headroom.)
82
- // T-095: Previously used Promise.allSettled() (parallel); switched to sequential to avoid 429s.
83
- for (const item of batch) {
84
- try {
85
- await this.extractFn(item.exchange, item.userId, item.sourceChunkId);
86
- }
87
- catch (err) {
88
- // Log but don't abort the batch — match original Promise.allSettled() behavior
89
- console.error('[plumb/extraction-queue] extractFn error:', err);
90
- }
91
- if (this.itemDelayMs > 0) {
92
- await new Promise(r => setTimeout(r, this.itemDelayMs));
93
- }
94
- }
95
- }
96
- finally {
97
- this.flushing = false;
98
- }
99
- }
100
- }
101
- //# sourceMappingURL=extraction-queue.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extraction-queue.js","sourceRoot":"","sources":["../src/extraction-queue.ts"],"names":[],"mappings":"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"}
@@ -1,22 +0,0 @@
1
- import type { MemoryStore } from './store.js';
2
- import { type Fact, type MessageExchange } from './types.js';
3
- /**
4
- * Extract facts from a conversation exchange via an LLM call.
5
- *
6
- * Makes one LLM call, parses the JSON array response, persists each fact via
7
- * the provided store, and returns the stored Fact[].
8
- *
9
- * Dedup strategy: always insert as a new entry — never update an existing fact
10
- * with the same subject+predicate. Decay scoring (T-006) handles ranking.
11
- *
12
- * @param exchange - The conversation exchange to extract facts from.
13
- * @param userId - The user ID to scope facts to (passed to store.store()).
14
- * NOTE: LocalStore captures userId at construction time, so
15
- * this param is accepted here for documentation/future use
16
- * but the store itself enforces the scope.
17
- * @param store - The MemoryStore instance to persist facts into.
18
- * @param llmFn - Optional LLM function to use (injectable for testing).
19
- * @param sourceChunkId - Optional raw_log chunk ID (T-079 processing state machine).
20
- */
21
- export declare function extractFacts(exchange: MessageExchange, _userId: string, store: MemoryStore, llmFn?: (prompt: string) => Promise<string>, sourceChunkId?: string): Promise<Fact[]>;
22
- //# sourceMappingURL=extractor.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,EAAa,KAAK,IAAI,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AA0JxE;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,WAAW,EAClB,KAAK,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAW,EACpD,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC,IAAI,EAAE,CAAC,CAwCjB"}
package/dist/extractor.js DELETED
@@ -1,188 +0,0 @@
1
- import { callLLM } from './llm-client.js';
2
- import { DecayRate } from './types.js';
3
- /** Build the extraction prompt from a conversation exchange. */
4
- function buildExtractionPrompt(exchange) {
5
- return (`Extract facts from this conversation exchange worth recalling in a future session.\n\n` +
6
- `Apply the 30-day recall test: "If I had no memory of this conversation and someone asked me about this user in 30 days, would knowing this fact help me serve them better?"\n\n` +
7
- `User: ${exchange.userMessage}\n` +
8
- `Agent: ${exchange.agentResponse}\n\n` +
9
- `Extraction tiers (prioritize stable, durable facts):\n` +
10
- `Tier 1 (always extract): identity, bio, stable preferences, permanent decisions, named relationships, skills/knowledge\n` +
11
- `Tier 2 (extract if confident): project architecture decisions, tool choices, recurring workflows\n` +
12
- `Tier 3 (skip unless explicitly significant): events, status updates, transient state, anything already tracked in a task or code\n\n` +
13
- `Concrete examples:\n` +
14
- `BAD (skip): "There are 2,162 pending raw_log chunks" — transient state, stale immediately\n` +
15
- `BAD (skip): "Claude Code ran T-018 independently" — event, already in git history\n` +
16
- `BAD (skip): "User registered plumb.run" — event, now complete\n` +
17
- `GOOD (keep): "Clay prefers bullet lists over markdown tables" — stable preference\n` +
18
- `GOOD (keep): "Plumb uses SQLite + WASM for local storage" — durable decision\n` +
19
- `GOOD (keep): "Clay's address is 332 Casper Drive, Lafayette, CO 80026" — identity/bio\n\n` +
20
- `Rules:\n` +
21
- `- Output ONLY a valid JSON array. No prose, no explanation, no markdown fences.\n` +
22
- `- Each item: {"subject": string, "predicate": string, "object": string, "context": string, "confidence": number 0-1, "decay_rate": "slow"|"medium"|"fast"}\n` +
23
- `- decay_rate: slow=identity/stable prefs/decisions, medium=project context/tool choices, fast=transient state\n` +
24
- `- confidence (calibrated — 0.95 is RARE, reserved for permanent facts):\n` +
25
- ` * 0.95: Stable biographical/identity facts, verified permanent data\n` +
26
- ` Example: "Clay lives at 332 Casper Drive, Lafayette, CO 80026" (permanent address)\n` +
27
- ` * 0.85-0.90: Strong stated preferences, confirmed architectural decisions, recurring behavioral patterns\n` +
28
- ` Example: "Clay prefers bullet lists over markdown tables" (stated preference, stable)\n` +
29
- ` * 0.70-0.84: Inferred preferences, situational context, likely-but-not-certain facts\n` +
30
- ` Example: "Clay seems to prefer morning standup calls" (inferred from behavior)\n` +
31
- ` * 0.50-0.69: Speculative observations, one-time mentions, might-be-outdated\n` +
32
- ` Example: "Clay might be considering switching to a new job" (speculative)\n` +
33
- ` * Below 0.50: Do NOT extract — output [] instead (not worth storing)\n` +
34
- `- Output [] liberally — it is better to extract nothing than to extract low-value facts that will pollute future retrieval.\n` +
35
- `- Skip: pleasantries, small talk, transient questions, tool outputs, error messages, current date/time statements, agent operating instructions (what the agent should do/read/reply), session identifiers, heartbeat/cron state, facts about the agent itself (unless the agent has a permanent attribute like a name or email), events already recorded elsewhere (git commits, kanban tasks, emails), transient project state\n` +
36
- `- Extract: identity/bio, stable preferences, permanent decisions, named relationships, skills/knowledge, project architecture (if durable), tool choices (if durable), recurring workflows\n\n` +
37
- `JSON array:`);
38
- }
39
- /**
40
- * Parse a JSON array from LLM output, tolerating markdown code fences and leading prose.
41
- * Returns empty array on any parse failure.
42
- */
43
- function parseJsonArray(text) {
44
- // Find the first '[' and last ']' — extract just that substring
45
- const start = text.indexOf('[');
46
- const end = text.lastIndexOf(']');
47
- if (start === -1 || end === -1 || end < start)
48
- return [];
49
- const jsonSlice = text.slice(start, end + 1).trim();
50
- const parsed = JSON.parse(jsonSlice);
51
- if (!Array.isArray(parsed))
52
- return [];
53
- return parsed;
54
- }
55
- function toDecayRate(raw) {
56
- if (raw === 'slow')
57
- return DecayRate.slow;
58
- if (raw === 'fast')
59
- return DecayRate.fast;
60
- return DecayRate.medium;
61
- }
62
- /**
63
- * Filter out ephemeral and agent-instruction facts that should not be stored.
64
- *
65
- * Blocks noise patterns:
66
- * - Current time/date statements (e.g., 'Current time is Friday...')
67
- * - Session identifiers (e.g., 'Current session is identified by...')
68
- * - Agent operating instructions (e.g., 'Agent should reply HEARTBEAT_OK')
69
- * - Heartbeat/cron state facts
70
- *
71
- * Does NOT block valid facts like:
72
- * - 'Agent is named Terra' (permanent attribute)
73
- * - 'Clay prefers agent to draft emails' (user preference)
74
- */
75
- function isNoiseFact(fact) {
76
- const subjectLower = fact.subject.toLowerCase();
77
- const predicateLower = fact.predicate.toLowerCase();
78
- const objectLower = fact.object.toLowerCase();
79
- // Block ephemeral timestamp subjects
80
- const blockedSubjects = [
81
- 'current time',
82
- 'current date',
83
- 'current session',
84
- 'today',
85
- "today's date",
86
- ];
87
- if (blockedSubjects.some(blocked => subjectLower.includes(blocked))) {
88
- return true;
89
- }
90
- // Block agent imperative predicates (instructions to the agent)
91
- const blockedPredicates = [
92
- 'should reply',
93
- 'must reply',
94
- 'should read',
95
- 'must read',
96
- 'should not infer',
97
- 'must not repeat',
98
- 'should not repeat',
99
- 'must not',
100
- 'should not',
101
- ];
102
- if (blockedPredicates.some(blocked => predicateLower.includes(blocked))) {
103
- return true;
104
- }
105
- // Block Agent + imperative (should/must) combinations
106
- // BUT allow 'Agent is named X' or 'Agent has email X' (permanent attributes)
107
- if (subjectLower === 'agent') {
108
- // If predicate is a permanent attribute verb, allow it
109
- const permanentPredicates = ['is named', 'has email', 'is called', 'name is', 'email is'];
110
- const isPermanentAttribute = permanentPredicates.some(perm => predicateLower.includes(perm));
111
- if (!isPermanentAttribute) {
112
- // Block agent operating instructions
113
- if (predicateLower.includes('should') || predicateLower.includes('must') ||
114
- predicateLower.includes('needs to') || predicateLower.includes('will')) {
115
- return true;
116
- }
117
- }
118
- }
119
- // Block date/time patterns in object field (e.g., '2026-03-06', 'Friday, March 6th, 2026')
120
- const dateTimePattern = /\d{4}-\d{2}-\d{2}|(monday|tuesday|wednesday|thursday|friday|saturday|sunday).*\d{4}|\d{1,2}:\d{2}\s*(am|pm)/i;
121
- if (predicateLower === 'is' && dateTimePattern.test(objectLower)) {
122
- return true;
123
- }
124
- // Block session identifier patterns
125
- if (objectLower.includes('openclaw session') || objectLower.includes('session 0x')) {
126
- return true;
127
- }
128
- // Block heartbeat-related facts
129
- if (subjectLower.includes('heartbeat') || objectLower.includes('heartbeat')) {
130
- return true;
131
- }
132
- return false;
133
- }
134
- /**
135
- * Extract facts from a conversation exchange via an LLM call.
136
- *
137
- * Makes one LLM call, parses the JSON array response, persists each fact via
138
- * the provided store, and returns the stored Fact[].
139
- *
140
- * Dedup strategy: always insert as a new entry — never update an existing fact
141
- * with the same subject+predicate. Decay scoring (T-006) handles ranking.
142
- *
143
- * @param exchange - The conversation exchange to extract facts from.
144
- * @param userId - The user ID to scope facts to (passed to store.store()).
145
- * NOTE: LocalStore captures userId at construction time, so
146
- * this param is accepted here for documentation/future use
147
- * but the store itself enforces the scope.
148
- * @param store - The MemoryStore instance to persist facts into.
149
- * @param llmFn - Optional LLM function to use (injectable for testing).
150
- * @param sourceChunkId - Optional raw_log chunk ID (T-079 processing state machine).
151
- */
152
- export async function extractFacts(exchange, _userId, store, llmFn = callLLM, sourceChunkId) {
153
- const prompt = buildExtractionPrompt(exchange);
154
- const response = await llmFn(prompt);
155
- let rawFacts;
156
- try {
157
- rawFacts = parseJsonArray(response);
158
- }
159
- catch {
160
- console.error('[plumb/extractor] Failed to parse LLM response as JSON array:', response);
161
- return [];
162
- }
163
- const facts = [];
164
- for (const raw of rawFacts) {
165
- // Post-extraction noise filter: discard ephemeral facts before storing
166
- if (isNoiseFact(raw)) {
167
- continue;
168
- }
169
- const factInput = {
170
- subject: String(raw.subject),
171
- predicate: String(raw.predicate),
172
- object: String(raw.object),
173
- confidence: Math.max(0, Math.min(1, Number(raw.confidence))),
174
- decayRate: toDecayRate(String(raw.decay_rate)),
175
- timestamp: new Date(),
176
- sourceSessionId: exchange.sessionId,
177
- ...(exchange.sessionLabel !== undefined ? { sourceSessionLabel: exchange.sessionLabel } : {}),
178
- ...(raw.context !== undefined ? { context: String(raw.context) } : {}),
179
- };
180
- // Dedup: always insert as a new entry (do not update existing same subject+predicate).
181
- // The store always inserts; decay scoring handles which entry wins at retrieval time.
182
- // T-079: Pass sourceChunkId to link fact back to raw_log chunk.
183
- const id = await store.store(factInput, sourceChunkId);
184
- facts.push({ id, ...factInput });
185
- }
186
- return facts;
187
- }
188
- //# sourceMappingURL=extractor.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=extractor.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.test.d.ts","sourceRoot":"","sources":["../src/extractor.test.ts"],"names":[],"mappings":""}
@@ -1,158 +0,0 @@
1
- import { test } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import { extractFacts } from './extractor.js';
4
- import { DecayRate } from './types.js';
5
- // ---------------------------------------------------------------------------
6
- // Test fixtures
7
- // ---------------------------------------------------------------------------
8
- const testExchange = {
9
- userMessage: 'I prefer dark mode for all my editors.',
10
- agentResponse: 'Noted, I will use dark mode settings.',
11
- timestamp: new Date('2026-01-01T12:00:00Z'),
12
- source: 'openclaw',
13
- sessionId: 'session-001',
14
- sessionLabel: 'prefs-chat',
15
- };
16
- const testExchangeNoLabel = {
17
- userMessage: 'I use TypeScript for everything.',
18
- agentResponse: 'Great choice.',
19
- timestamp: new Date('2026-01-01T13:00:00Z'),
20
- source: 'openclaw',
21
- sessionId: 'session-002',
22
- };
23
- // ---------------------------------------------------------------------------
24
- // In-memory mock store
25
- // ---------------------------------------------------------------------------
26
- function createMockStore() {
27
- const stored = [];
28
- const store = {
29
- async store(fact) {
30
- const id = crypto.randomUUID();
31
- stored.push({ id, ...fact });
32
- return id;
33
- },
34
- async search(_query, _limit) {
35
- return [];
36
- },
37
- async delete(_id) { },
38
- async status() {
39
- return { factCount: stored.length, rawLogCount: 0, lastIngestion: null, storageBytes: 0 };
40
- },
41
- async ingest(_exchange) {
42
- return { rawLogId: crypto.randomUUID(), factsExtracted: 0, factIds: [] };
43
- },
44
- };
45
- return { store, stored };
46
- }
47
- // ---------------------------------------------------------------------------
48
- // LLM mock helpers
49
- // ---------------------------------------------------------------------------
50
- function mockLLM(responseJson) {
51
- return async (_prompt) => JSON.stringify(responseJson);
52
- }
53
- function mockLLMWithFences(responseJson) {
54
- return async (_prompt) => '```json\n' + JSON.stringify(responseJson) + '\n```';
55
- }
56
- // ---------------------------------------------------------------------------
57
- // Tests: structured output parsing
58
- // ---------------------------------------------------------------------------
59
- test('extractFacts: parses a valid JSON array and stores each fact', async () => {
60
- const { store, stored } = createMockStore();
61
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([
62
- {
63
- subject: 'user',
64
- predicate: 'prefers',
65
- object: 'dark mode',
66
- context: 'mentioned for all editors',
67
- confidence: 0.9,
68
- decay_rate: 'slow',
69
- },
70
- ]));
71
- assert.equal(result.length, 1);
72
- assert.equal(result[0].subject, 'user');
73
- assert.equal(result[0].predicate, 'prefers');
74
- assert.equal(result[0].object, 'dark mode');
75
- assert.equal(result[0].context, 'mentioned for all editors');
76
- assert.equal(result[0].confidence, 0.9);
77
- assert.equal(result[0].decayRate, DecayRate.slow);
78
- assert.equal(result[0].sourceSessionId, 'session-001');
79
- assert.equal(result[0].sourceSessionLabel, 'prefs-chat');
80
- assert.match(result[0].id, /^[0-9a-f-]{36}$/);
81
- assert.equal(stored.length, 1);
82
- });
83
- test('extractFacts: strips markdown code fences before parsing', async () => {
84
- const { store } = createMockStore();
85
- const result = await extractFacts(testExchange, 'user-1', store, mockLLMWithFences([
86
- { subject: 'user', predicate: 'uses', object: 'TypeScript', confidence: 0.95, decay_rate: 'medium' },
87
- ]));
88
- assert.equal(result.length, 1);
89
- assert.equal(result[0].object, 'TypeScript');
90
- assert.equal(result[0].decayRate, DecayRate.medium);
91
- });
92
- test('extractFacts: returns [] and stores nothing when LLM returns []', async () => {
93
- const { store, stored } = createMockStore();
94
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([]));
95
- assert.equal(result.length, 0);
96
- assert.equal(stored.length, 0);
97
- });
98
- test('extractFacts: returns [] gracefully when LLM returns invalid JSON', async () => {
99
- const { store, stored } = createMockStore();
100
- const result = await extractFacts(testExchange, 'user-1', store, async () => 'This is definitely not JSON.');
101
- assert.equal(result.length, 0);
102
- assert.equal(stored.length, 0);
103
- });
104
- test('extractFacts: handles multiple facts in one LLM response', async () => {
105
- const { store, stored } = createMockStore();
106
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([
107
- { subject: 'user', predicate: 'prefers', object: 'dark mode', confidence: 0.9, decay_rate: 'slow' },
108
- { subject: 'user', predicate: 'uses', object: 'vim keybindings', confidence: 0.8, decay_rate: 'medium' },
109
- ]));
110
- assert.equal(result.length, 2);
111
- assert.equal(stored.length, 2);
112
- });
113
- test('extractFacts: clamps confidence to [0, 1]', async () => {
114
- const { store } = createMockStore();
115
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([
116
- { subject: 'a', predicate: 'b', object: 'c', confidence: 1.5, decay_rate: 'slow' },
117
- { subject: 'd', predicate: 'e', object: 'f', confidence: -0.3, decay_rate: 'fast' },
118
- ]));
119
- assert.equal(result[0].confidence, 1.0);
120
- assert.equal(result[1].confidence, 0.0);
121
- });
122
- test('extractFacts: unknown decay_rate defaults to medium', async () => {
123
- const { store } = createMockStore();
124
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([{ subject: 'a', predicate: 'b', object: 'c', confidence: 0.5, decay_rate: 'unknown-rate' }]));
125
- assert.equal(result[0].decayRate, DecayRate.medium);
126
- });
127
- test('extractFacts: sourceSessionLabel is omitted when exchange has no sessionLabel', async () => {
128
- const { store } = createMockStore();
129
- const result = await extractFacts(testExchangeNoLabel, 'user-1', store, mockLLM([{ subject: 'user', predicate: 'uses', object: 'TypeScript', confidence: 0.9, decay_rate: 'slow' }]));
130
- assert.equal(result[0].sourceSessionId, 'session-002');
131
- assert.equal(result[0].sourceSessionLabel, undefined);
132
- });
133
- test('extractFacts: context field is omitted when LLM does not include it', async () => {
134
- const { store } = createMockStore();
135
- const result = await extractFacts(testExchange, 'user-1', store, mockLLM([{ subject: 'user', predicate: 'prefers', object: 'dark mode', confidence: 0.9, decay_rate: 'slow' }]));
136
- assert.equal(result[0].context, undefined);
137
- });
138
- // ---------------------------------------------------------------------------
139
- // Tests: deduplication strategy
140
- // ---------------------------------------------------------------------------
141
- test('extractFacts dedup: inserts new entry when same subject+predicate already exists', async () => {
142
- const { store, stored } = createMockStore();
143
- const singleFact = [
144
- { subject: 'user', predicate: 'prefers', object: 'dark mode', confidence: 0.9, decay_rate: 'slow' },
145
- ];
146
- // First extraction
147
- await extractFacts(testExchange, 'user-1', store, mockLLM(singleFact));
148
- assert.equal(stored.length, 1);
149
- // Second extraction with same subject+predicate
150
- await extractFacts(testExchange, 'user-1', store, mockLLM(singleFact));
151
- assert.equal(stored.length, 2, 'should insert new entry, not overwrite existing');
152
- // Both entries have unique IDs
153
- assert.notEqual(stored[0].id, stored[1].id);
154
- // Both have same subject+predicate
155
- assert.equal(stored[0].subject, stored[1].subject);
156
- assert.equal(stored[0].predicate, stored[1].predicate);
157
- });
158
- //# sourceMappingURL=extractor.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extractor.test.js","sourceRoot":"","sources":["../src/extractor.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEvC,8EAA8E;AAC9E,gBAAgB;AAChB,8EAA8E;AAE9E,MAAM,YAAY,GAAoB;IACpC,WAAW,EAAE,wCAAwC;IACrD,aAAa,EAAE,uCAAuC;IACtD,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;IAC3C,MAAM,EAAE,UAAU;IAClB,SAAS,EAAE,aAAa;IACxB,YAAY,EAAE,YAAY;CAC3B,CAAC;AAEF,MAAM,mBAAmB,GAAoB;IAC3C,WAAW,EAAE,kCAAkC;IAC/C,aAAa,EAAE,eAAe;IAC9B,SAAS,EAAE,IAAI,IAAI,CAAC,sBAAsB,CAAC;IAC3C,MAAM,EAAE,UAAU;IAClB,SAAS,EAAE,aAAa;CACzB,CAAC;AAEF,8EAA8E;AAC9E,uBAAuB;AACvB,8EAA8E;AAE9E,SAAS,eAAe;IACtB,MAAM,MAAM,GAAW,EAAE,CAAC;IAE1B,MAAM,KAAK,GAAgB;QACzB,KAAK,CAAC,KAAK,CAAC,IAAsB;YAChC,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI,EAAE,CAAC,CAAC;YAC7B,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,MAAc,EAAE,MAAe;YAC1C,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAW,IAAkB,CAAC;QAC3C,KAAK,CAAC,MAAM;YACV,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QAC5F,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,SAA0B;YACrC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,EAAE,cAAc,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC3E,CAAC;KACF,CAAC;IAEF,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC3B,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,OAAO,CAAC,YAAqB;IACpC,OAAO,KAAK,EAAE,OAAe,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,iBAAiB,CAAC,YAAqB;IAC9C,OAAO,KAAK,EAAE,OAAe,EAAE,EAAE,CAAC,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC;AACzF,CAAC;AAED,8EAA8E;AAC9E,mCAAmC;AACnC,8EAA8E;AAE9E,IAAI,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;IAC9E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,OAAO,CAAC;QACN;YACE,OAAO,EAAE,MAAM;YACf,SAAS,EAAE,SAAS;YACpB,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,2BAA2B;YACpC,UAAU,EAAE,GAAG;YACf,UAAU,EAAE,MAAM;SACnB;KACF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,2BAA2B,CAAC,CAAC;IAC9D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;IACnD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,iBAAiB,CAAC;QAChB,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE;KACrG,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC9C,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;IACjF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;IACnF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAC5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,KAAK,IAAI,EAAE,CAAC,8BAA8B,CAC3C,CAAC;IACF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;IAC1E,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAE5C,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,OAAO,CAAC;QACN,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE;QACnG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE;KACzG,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAC/B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;AACjC,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;IAC3D,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,OAAO,CAAC;QACN,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE;QAClF,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE;KACpF,CAAC,CACH,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,KAAK,IAAI,EAAE;IACrE,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC,CAAC,CACtG,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+EAA+E,EAAE,KAAK,IAAI,EAAE;IAC/F,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,mBAAmB,EACnB,QAAQ,EACR,KAAK,EACL,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAC7G,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,eAAe,EAAE,aAAa,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qEAAqE,EAAE,KAAK,IAAI,EAAE;IACrF,MAAM,EAAE,KAAK,EAAE,GAAG,eAAe,EAAE,CAAC;IAEpC,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,YAAY,EACZ,QAAQ,EACR,KAAK,EACL,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC,CAC/G,CAAC;IAEF,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAC9C,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,gCAAgC;AAChC,8EAA8E;AAE9E,IAAI,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;IAClG,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,eAAe,EAAE,CAAC;IAE5C,MAAM,UAAU,GAAG;QACjB,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM,EAAE;KACpG,CAAC;IAEF,mBAAmB;IACnB,MAAM,YAAY,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAE/B,gDAAgD;IAChD,MAAM,YAAY,CAAC,YAAY,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;IACvE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,iDAAiD,CAAC,CAAC;IAElF,+BAA+B;IAC/B,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC,CAAC;IAE9C,mCAAmC;IACnC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAE,CAAC,SAAS,CAAC,CAAC;AAC3D,CAAC,CAAC,CAAC"}
@@ -1,32 +0,0 @@
1
- /**
2
- * Fact hybrid search (Layer 2 retrieval).
3
- *
4
- * Pipeline:
5
- * 1. BM25 keyword search over concatenated fact text (subject+predicate+object+context)
6
- * 2. KNN vector search via JS cosine similarity (replaces sqlite-vec)
7
- * 3. Reciprocal Rank Fusion (RRF, k=60) merges both ranked lists
8
- * 4. Recency decay: score *= e^(-lambda × age_in_days), using per-fact decay_rate
9
- * 5. Cross-encoder reranker (top-20 candidates → Xenova/ms-marco-MiniLM-L-6-v2)
10
- * 6. Return top-k by reranker score (falls back to RRF×decay if reranker fails)
11
- *
12
- * Decay lambdas by fact decay_rate:
13
- * slow=0.003 (half-life ~231 days) — identity, stable preferences
14
- * medium=0.012 (half-life ~58 days) — project context, tool choices
15
- * fast=0.05 (half-life ~14 days) — transient state
16
- */
17
- import type { WasmDb } from './wasm-db.js';
18
- import type { SearchResult } from './types.js';
19
- /**
20
- * Hybrid search over facts.
21
- *
22
- * @param db The WASM SQLite Database instance
23
- * @param userId Scopes the search to this user's data
24
- * @param query Natural language query string
25
- * @param limit Number of results to return (default 20)
26
- * @param preloadedCorpus Optional pre-loaded vec_facts corpus (T-096: in-memory cache)
27
- */
28
- export declare function searchFacts(db: WasmDb, userId: string, query: string, limit?: number, preloadedCorpus?: Array<{
29
- rowid: number;
30
- embedding: Float32Array;
31
- }>): Promise<readonly SearchResult[]>;
32
- //# sourceMappingURL=fact-search.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"fact-search.d.ts","sourceRoot":"","sources":["../src/fact-search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAG3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA0F/C;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAC/B,EAAE,EAAE,MAAM,EACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,KAAK,SAAK,EACV,eAAe,CAAC,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAAC,GAClE,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CA2GlC"}