@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.
- package/package.json +1 -1
- package/dist/extraction-queue.d.ts +0 -72
- package/dist/extraction-queue.d.ts.map +0 -1
- package/dist/extraction-queue.js +0 -101
- package/dist/extraction-queue.js.map +0 -1
- package/dist/extractor.d.ts +0 -22
- package/dist/extractor.d.ts.map +0 -1
- package/dist/extractor.js +0 -188
- package/dist/extractor.js.map +0 -1
- package/dist/extractor.test.d.ts +0 -2
- package/dist/extractor.test.d.ts.map +0 -1
- package/dist/extractor.test.js +0 -158
- package/dist/extractor.test.js.map +0 -1
- package/dist/fact-search.d.ts +0 -32
- package/dist/fact-search.d.ts.map +0 -1
- package/dist/fact-search.js +0 -174
- package/dist/fact-search.js.map +0 -1
- package/dist/fact-search.test.d.ts +0 -12
- package/dist/fact-search.test.d.ts.map +0 -1
- package/dist/fact-search.test.js +0 -117
- package/dist/fact-search.test.js.map +0 -1
- package/dist/llm-client.d.ts +0 -59
- package/dist/llm-client.d.ts.map +0 -1
- package/dist/llm-client.js +0 -227
- package/dist/llm-client.js.map +0 -1
- package/dist/local-store.test.d.ts +0 -2
- package/dist/local-store.test.d.ts.map +0 -1
- package/dist/local-store.test.js +0 -146
- package/dist/local-store.test.js.map +0 -1
- package/dist/raw-log-search.test.d.ts +0 -12
- package/dist/raw-log-search.test.d.ts.map +0 -1
- package/dist/raw-log-search.test.js +0 -124
- package/dist/raw-log-search.test.js.map +0 -1
- package/dist/read-path.test.d.ts +0 -15
- package/dist/read-path.test.d.ts.map +0 -1
- package/dist/read-path.test.js +0 -393
- package/dist/read-path.test.js.map +0 -1
- package/dist/scorer.test.d.ts +0 -10
- package/dist/scorer.test.d.ts.map +0 -1
- package/dist/scorer.test.js +0 -169
- package/dist/scorer.test.js.map +0 -1
package/package.json
CHANGED
|
@@ -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"}
|
package/dist/extraction-queue.js
DELETED
|
@@ -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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"}
|
package/dist/extractor.d.ts
DELETED
|
@@ -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
|
package/dist/extractor.d.ts.map
DELETED
|
@@ -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
|
package/dist/extractor.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"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"}
|
package/dist/extractor.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.test.d.ts","sourceRoot":"","sources":["../src/extractor.test.ts"],"names":[],"mappings":""}
|
package/dist/extractor.test.js
DELETED
|
@@ -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":"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"}
|
package/dist/fact-search.d.ts
DELETED
|
@@ -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"}
|