@getplumb/core 0.1.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/embedder.d.ts +16 -2
- package/dist/embedder.d.ts.map +1 -1
- package/dist/embedder.js +23 -4
- package/dist/embedder.js.map +1 -1
- package/dist/extraction-queue.d.ts +13 -3
- package/dist/extraction-queue.d.ts.map +1 -1
- package/dist/extraction-queue.js +21 -4
- package/dist/extraction-queue.js.map +1 -1
- package/dist/extractor.d.ts +2 -1
- package/dist/extractor.d.ts.map +1 -1
- package/dist/extractor.js +106 -7
- package/dist/extractor.js.map +1 -1
- package/dist/extractor.test.d.ts +2 -0
- package/dist/extractor.test.d.ts.map +1 -0
- package/dist/extractor.test.js +158 -0
- package/dist/extractor.test.js.map +1 -0
- package/dist/fact-search.d.ts +9 -5
- package/dist/fact-search.d.ts.map +1 -1
- package/dist/fact-search.js +25 -16
- package/dist/fact-search.js.map +1 -1
- package/dist/fact-search.test.d.ts +12 -0
- package/dist/fact-search.test.d.ts.map +1 -0
- package/dist/fact-search.test.js +117 -0
- package/dist/fact-search.test.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm-client.d.ts +11 -2
- package/dist/llm-client.d.ts.map +1 -1
- package/dist/llm-client.js +47 -3
- package/dist/llm-client.js.map +1 -1
- package/dist/local-store.d.ts +32 -1
- package/dist/local-store.d.ts.map +1 -1
- package/dist/local-store.js +510 -35
- package/dist/local-store.js.map +1 -1
- package/dist/local-store.test.d.ts +2 -0
- package/dist/local-store.test.d.ts.map +1 -0
- package/dist/local-store.test.js +146 -0
- package/dist/local-store.test.js.map +1 -0
- package/dist/raw-log-search.d.ts +9 -5
- package/dist/raw-log-search.d.ts.map +1 -1
- package/dist/raw-log-search.js +107 -29
- package/dist/raw-log-search.js.map +1 -1
- package/dist/raw-log-search.test.d.ts +12 -0
- package/dist/raw-log-search.test.d.ts.map +1 -0
- package/dist/raw-log-search.test.js +124 -0
- package/dist/raw-log-search.test.js.map +1 -0
- package/dist/read-path.test.d.ts +15 -0
- package/dist/read-path.test.d.ts.map +1 -0
- package/dist/read-path.test.js +393 -0
- package/dist/read-path.test.js.map +1 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +58 -1
- package/dist/schema.js.map +1 -1
- package/dist/scorer.test.d.ts +10 -0
- package/dist/scorer.test.d.ts.map +1 -0
- package/dist/scorer.test.js +169 -0
- package/dist/scorer.test.js.map +1 -0
- package/dist/store.d.ts +3 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/wasm-db.d.ts +63 -8
- package/dist/wasm-db.d.ts.map +1 -1
- package/dist/wasm-db.js +124 -31
- package/dist/wasm-db.js.map +1 -1
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Cross-session AI memory — storage abstraction, types, and local SQLite driver
|
|
4
4
|
|
|
5
|
-
The core library for [Plumb](https://
|
|
5
|
+
The core library for [Plumb](https://plumb.run) — a local-first, MCP-native memory layer for AI agents.
|
|
6
6
|
|
|
7
7
|
## What it does
|
|
8
8
|
|
|
@@ -42,7 +42,7 @@ const results = await store.searchRawLog('TypeScript preferences', 5);
|
|
|
42
42
|
|
|
43
43
|
- [Docs](https://docs.getplumb.dev)
|
|
44
44
|
- [GitHub](https://github.com/getplumb/plumb)
|
|
45
|
-
- [
|
|
45
|
+
- [plumb.run](https://plumb.run)
|
|
46
46
|
|
|
47
47
|
## License
|
|
48
48
|
|
package/dist/embedder.d.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
export declare const EMBED_DIM = 384;
|
|
17
17
|
/**
|
|
18
18
|
* Embed a passage for indexing (no query prefix).
|
|
19
|
-
* Returns a normalized Float32Array of length EMBED_DIM
|
|
20
|
-
* if @xenova/transformers is not available
|
|
19
|
+
* Returns a normalized Float32Array of length EMBED_DIM.
|
|
20
|
+
* Throws if @xenova/transformers is not available or embedding fails.
|
|
21
21
|
*/
|
|
22
22
|
export declare function embed(text: string): Promise<Float32Array>;
|
|
23
23
|
/**
|
|
@@ -33,4 +33,18 @@ export declare function embedQuery(query: string): Promise<Float32Array>;
|
|
|
33
33
|
* should detect all-zero arrays and fall back to RRF order.
|
|
34
34
|
*/
|
|
35
35
|
export declare function rerankScores(query: string, passages: string[]): Promise<number[]>;
|
|
36
|
+
/**
|
|
37
|
+
* Warm the embedder pipeline at initialization time.
|
|
38
|
+
* Loads and JIT-compiles the Xenova model to eliminate first-query cold-start latency.
|
|
39
|
+
* No-op if @xenova/transformers is unavailable.
|
|
40
|
+
*/
|
|
41
|
+
export declare function warmEmbedder(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Warm the reranker pipeline at initialization time.
|
|
44
|
+
* Loads and JIT-compiles the cross-encoder model (~80MB) to eliminate first-query cold-start latency.
|
|
45
|
+
* Adds ~200ms to startup and increases memory footprint, but ensures consistent <250ms query performance
|
|
46
|
+
* from the first query onward (without warming, first query sees ~360ms, subsequent queries ~210ms).
|
|
47
|
+
* No-op if @xenova/transformers is unavailable.
|
|
48
|
+
*/
|
|
49
|
+
export declare function warmReranker(): Promise<void>;
|
|
36
50
|
//# sourceMappingURL=embedder.d.ts.map
|
package/dist/embedder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,sDAAsD;AACtD,eAAO,MAAM,SAAS,MAAM,CAAC;AA2B7B;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,sDAAsD;AACtD,eAAO,MAAM,SAAS,MAAM,CAAC;AA2B7B;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAO/D;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAKrE;AA+BD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAiBvF;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD"}
|
package/dist/embedder.js
CHANGED
|
@@ -39,13 +39,14 @@ async function getEmbedPipeline() {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Embed a passage for indexing (no query prefix).
|
|
42
|
-
* Returns a normalized Float32Array of length EMBED_DIM
|
|
43
|
-
* if @xenova/transformers is not available
|
|
42
|
+
* Returns a normalized Float32Array of length EMBED_DIM.
|
|
43
|
+
* Throws if @xenova/transformers is not available or embedding fails.
|
|
44
44
|
*/
|
|
45
45
|
export async function embed(text) {
|
|
46
46
|
const pipe = await getEmbedPipeline();
|
|
47
|
-
if (pipe === null)
|
|
48
|
-
|
|
47
|
+
if (pipe === null) {
|
|
48
|
+
throw new Error('Embedder not available: @xenova/transformers failed to load');
|
|
49
|
+
}
|
|
49
50
|
const output = await pipe(text, { pooling: 'mean', normalize: true });
|
|
50
51
|
return new Float32Array(output.data);
|
|
51
52
|
}
|
|
@@ -104,4 +105,22 @@ export async function rerankScores(query, passages) {
|
|
|
104
105
|
}
|
|
105
106
|
return scores;
|
|
106
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Warm the embedder pipeline at initialization time.
|
|
110
|
+
* Loads and JIT-compiles the Xenova model to eliminate first-query cold-start latency.
|
|
111
|
+
* No-op if @xenova/transformers is unavailable.
|
|
112
|
+
*/
|
|
113
|
+
export async function warmEmbedder() {
|
|
114
|
+
await getEmbedPipeline();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Warm the reranker pipeline at initialization time.
|
|
118
|
+
* Loads and JIT-compiles the cross-encoder model (~80MB) to eliminate first-query cold-start latency.
|
|
119
|
+
* Adds ~200ms to startup and increases memory footprint, but ensures consistent <250ms query performance
|
|
120
|
+
* from the first query onward (without warming, first query sees ~360ms, subsequent queries ~210ms).
|
|
121
|
+
* No-op if @xenova/transformers is unavailable.
|
|
122
|
+
*/
|
|
123
|
+
export async function warmReranker() {
|
|
124
|
+
await getRerankPipeline();
|
|
125
|
+
}
|
|
107
126
|
//# sourceMappingURL=embedder.js.map
|
package/dist/embedder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embedder.js","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,sDAAsD;AACtD,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAI7B,IAAI,cAAc,GAAoB,IAAI,CAAC;AAC3C,IAAI,gBAAgB,GAAG,KAAK,CAAC;AAE7B,KAAK,UAAU,gBAAgB;IAC7B,IAAI,gBAAgB;QAAE,OAAO,IAAI,CAAC;IAClC,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,4EAA4E;YAC5E,6EAA6E;YAC7E,4DAA4D;YAC5D,6DAA6D;YAC7D,2DAA2D;YAC3D,MAAM,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAC;YAC9D,GAAqC,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAC/D,cAAc,GAAG,CAAC,MAAM,QAAQ,CAAC,oBAAoB,EAAE,0BAA0B,CAAC,CAAa,CAAC;QAClG,CAAC;QAAC,MAAM,CAAC;YACP,gBAAgB,GAAG,IAAI,CAAC;YACxB,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,MAAM,gBAAgB,EAAE,CAAC;IACtC,IAAI,IAAI,KAAK,IAAI;
|
|
1
|
+
{"version":3,"file":"embedder.js","sourceRoot":"","sources":["../src/embedder.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,22 @@
|
|
|
1
1
|
import type { MessageExchange, Fact } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Extraction function signature expected by ExtractionQueue.
|
|
4
|
-
* Takes an exchange and
|
|
4
|
+
* Takes an exchange, userId, and sourceChunkId, returns extracted facts.
|
|
5
5
|
* LocalStore binds extractFacts with its own store + llmConfig.
|
|
6
|
+
* T-079: Added sourceChunkId to link extracted facts back to raw_log chunk.
|
|
6
7
|
*/
|
|
7
|
-
export type ExtractFn = (exchange: MessageExchange, userId: string) => Promise<Fact[]>;
|
|
8
|
+
export type ExtractFn = (exchange: MessageExchange, userId: string, sourceChunkId: string) => Promise<Fact[]>;
|
|
8
9
|
export interface ExtractionQueueOptions {
|
|
9
10
|
/** Drain interval in milliseconds. Defaults to PLUMB_EXTRACT_INTERVAL_MS env var or 300000 (5 min). */
|
|
10
11
|
intervalMs?: number;
|
|
11
12
|
/** Max queue size before early flush. Defaults to PLUMB_EXTRACT_BATCH_SIZE env var or 10. */
|
|
12
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;
|
|
13
20
|
}
|
|
14
21
|
/**
|
|
15
22
|
* ExtractionQueue — batched fact extraction queue.
|
|
@@ -35,13 +42,15 @@ export declare class ExtractionQueue {
|
|
|
35
42
|
private timer;
|
|
36
43
|
private readonly intervalMs;
|
|
37
44
|
private readonly batchSize;
|
|
45
|
+
private readonly itemDelayMs;
|
|
38
46
|
private flushing;
|
|
39
47
|
constructor(extractFn: ExtractFn, opts?: ExtractionQueueOptions);
|
|
40
48
|
/**
|
|
41
49
|
* Enqueue an exchange for fact extraction.
|
|
42
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.
|
|
43
52
|
*/
|
|
44
|
-
enqueue(exchange: MessageExchange, userId: string): void;
|
|
53
|
+
enqueue(exchange: MessageExchange, userId: string, sourceChunkId: string): void;
|
|
45
54
|
/**
|
|
46
55
|
* Start the background drain loop.
|
|
47
56
|
* Call this once after construction (e.g., in plugin activate()).
|
|
@@ -56,6 +65,7 @@ export declare class ExtractionQueue {
|
|
|
56
65
|
* Flush the queue immediately: drain all pending items and call extractFn for each.
|
|
57
66
|
* Uses Promise.allSettled() so one failed extraction doesn't drop others.
|
|
58
67
|
* Safe to call concurrently — only one flush runs at a time.
|
|
68
|
+
* T-079: Pass sourceChunkId to extractFn for processing state machine.
|
|
59
69
|
*/
|
|
60
70
|
flush(): Promise<void>;
|
|
61
71
|
}
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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
CHANGED
|
@@ -22,18 +22,21 @@ export class ExtractionQueue {
|
|
|
22
22
|
timer = null;
|
|
23
23
|
intervalMs;
|
|
24
24
|
batchSize;
|
|
25
|
+
itemDelayMs;
|
|
25
26
|
flushing = false; // Prevent concurrent flush() calls
|
|
26
27
|
constructor(extractFn, opts) {
|
|
27
28
|
this.extractFn = extractFn;
|
|
28
29
|
this.intervalMs = opts?.intervalMs ?? Number(process.env.PLUMB_EXTRACT_INTERVAL_MS ?? 300_000);
|
|
29
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);
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
34
|
* Enqueue an exchange for fact extraction.
|
|
33
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.
|
|
34
37
|
*/
|
|
35
|
-
enqueue(exchange, userId) {
|
|
36
|
-
this.queue.push({ exchange, userId });
|
|
38
|
+
enqueue(exchange, userId, sourceChunkId) {
|
|
39
|
+
this.queue.push({ exchange, userId, sourceChunkId });
|
|
37
40
|
if (this.queue.length >= this.batchSize) {
|
|
38
41
|
void this.flush();
|
|
39
42
|
}
|
|
@@ -62,6 +65,7 @@ export class ExtractionQueue {
|
|
|
62
65
|
* Flush the queue immediately: drain all pending items and call extractFn for each.
|
|
63
66
|
* Uses Promise.allSettled() so one failed extraction doesn't drop others.
|
|
64
67
|
* Safe to call concurrently — only one flush runs at a time.
|
|
68
|
+
* T-079: Pass sourceChunkId to extractFn for processing state machine.
|
|
65
69
|
*/
|
|
66
70
|
async flush() {
|
|
67
71
|
// Prevent concurrent flush() calls
|
|
@@ -73,8 +77,21 @@ export class ExtractionQueue {
|
|
|
73
77
|
const batch = this.queue.splice(0);
|
|
74
78
|
if (batch.length === 0)
|
|
75
79
|
return;
|
|
76
|
-
// Extract facts
|
|
77
|
-
|
|
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
|
+
}
|
|
78
95
|
}
|
|
79
96
|
finally {
|
|
80
97
|
this.flushing = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction-queue.js","sourceRoot":"","sources":["../src/extraction-queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"extraction-queue.js","sourceRoot":"","sources":["../src/extraction-queue.ts"],"names":[],"mappings":"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"}
|
package/dist/extractor.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { type Fact, type MessageExchange } from './types.js';
|
|
|
16
16
|
* but the store itself enforces the scope.
|
|
17
17
|
* @param store - The MemoryStore instance to persist facts into.
|
|
18
18
|
* @param llmFn - Optional LLM function to use (injectable for testing).
|
|
19
|
+
* @param sourceChunkId - Optional raw_log chunk ID (T-079 processing state machine).
|
|
19
20
|
*/
|
|
20
|
-
export declare function extractFacts(exchange: MessageExchange, _userId: string, store: MemoryStore, llmFn?: (prompt: string) => Promise<string
|
|
21
|
+
export declare function extractFacts(exchange: MessageExchange, _userId: string, store: MemoryStore, llmFn?: (prompt: string) => Promise<string>, sourceChunkId?: string): Promise<Fact[]>;
|
|
21
22
|
//# sourceMappingURL=extractor.d.ts.map
|
package/dist/extractor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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
CHANGED
|
@@ -2,17 +2,38 @@ import { callLLM } from './llm-client.js';
|
|
|
2
2
|
import { DecayRate } from './types.js';
|
|
3
3
|
/** Build the extraction prompt from a conversation exchange. */
|
|
4
4
|
function buildExtractionPrompt(exchange) {
|
|
5
|
-
return (`Extract facts from this conversation exchange worth
|
|
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` +
|
|
6
7
|
`User: ${exchange.userMessage}\n` +
|
|
7
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` +
|
|
8
20
|
`Rules:\n` +
|
|
9
21
|
`- Output ONLY a valid JSON array. No prose, no explanation, no markdown fences.\n` +
|
|
10
22
|
`- Each item: {"subject": string, "predicate": string, "object": string, "context": string, "confidence": number 0-1, "decay_rate": "slow"|"medium"|"fast"}\n` +
|
|
11
23
|
`- decay_rate: slow=identity/stable prefs/decisions, medium=project context/tool choices, fast=transient state\n` +
|
|
12
|
-
`- confidence
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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` +
|
|
16
37
|
`JSON array:`);
|
|
17
38
|
}
|
|
18
39
|
/**
|
|
@@ -38,6 +59,78 @@ function toDecayRate(raw) {
|
|
|
38
59
|
return DecayRate.fast;
|
|
39
60
|
return DecayRate.medium;
|
|
40
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
|
+
}
|
|
41
134
|
/**
|
|
42
135
|
* Extract facts from a conversation exchange via an LLM call.
|
|
43
136
|
*
|
|
@@ -54,8 +147,9 @@ function toDecayRate(raw) {
|
|
|
54
147
|
* but the store itself enforces the scope.
|
|
55
148
|
* @param store - The MemoryStore instance to persist facts into.
|
|
56
149
|
* @param llmFn - Optional LLM function to use (injectable for testing).
|
|
150
|
+
* @param sourceChunkId - Optional raw_log chunk ID (T-079 processing state machine).
|
|
57
151
|
*/
|
|
58
|
-
export async function extractFacts(exchange, _userId, store, llmFn = callLLM) {
|
|
152
|
+
export async function extractFacts(exchange, _userId, store, llmFn = callLLM, sourceChunkId) {
|
|
59
153
|
const prompt = buildExtractionPrompt(exchange);
|
|
60
154
|
const response = await llmFn(prompt);
|
|
61
155
|
let rawFacts;
|
|
@@ -68,6 +162,10 @@ export async function extractFacts(exchange, _userId, store, llmFn = callLLM) {
|
|
|
68
162
|
}
|
|
69
163
|
const facts = [];
|
|
70
164
|
for (const raw of rawFacts) {
|
|
165
|
+
// Post-extraction noise filter: discard ephemeral facts before storing
|
|
166
|
+
if (isNoiseFact(raw)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
71
169
|
const factInput = {
|
|
72
170
|
subject: String(raw.subject),
|
|
73
171
|
predicate: String(raw.predicate),
|
|
@@ -81,7 +179,8 @@ export async function extractFacts(exchange, _userId, store, llmFn = callLLM) {
|
|
|
81
179
|
};
|
|
82
180
|
// Dedup: always insert as a new entry (do not update existing same subject+predicate).
|
|
83
181
|
// The store always inserts; decay scoring handles which entry wins at retrieval time.
|
|
84
|
-
|
|
182
|
+
// T-079: Pass sourceChunkId to link fact back to raw_log chunk.
|
|
183
|
+
const id = await store.store(factInput, sourceChunkId);
|
|
85
184
|
facts.push({ id, ...factInput });
|
|
86
185
|
}
|
|
87
186
|
return facts;
|
package/dist/extractor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAE,SAAS,EAAmC,MAAM,YAAY,CAAC;AAYxE,gEAAgE;AAChE,SAAS,qBAAqB,CAAC,QAAyB;IACtD,OAAO,CACL,
|
|
1
|
+
{"version":3,"file":"extractor.js","sourceRoot":"","sources":["../src/extractor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.test.d.ts","sourceRoot":"","sources":["../src/extractor.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extractor.test.js","sourceRoot":"","sources":["../src/extractor.test.ts"],"names":[],"mappings":"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"}
|