@plur-ai/core 0.7.7 → 0.8.2

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.
@@ -0,0 +1,184 @@
1
+ import {
2
+ appendHistory,
3
+ buildDedupPrompt,
4
+ computeContentHash,
5
+ loadEngrams,
6
+ logger,
7
+ parseDedupResponse,
8
+ saveEngrams
9
+ } from "./chunk-GRDNBUIJ.js";
10
+ import {
11
+ withLock
12
+ } from "./chunk-MY4XVDCE.js";
13
+ import "./chunk-2ZDO52B4.js";
14
+
15
+ // src/learn-async.ts
16
+ function executeDedupDecision(deps, statement, context, decision, targetId, conflicts) {
17
+ switch (decision) {
18
+ case "NOOP": {
19
+ if (targetId) {
20
+ const existing = deps.getById(targetId);
21
+ if (existing) return { engram: existing, decision: "NOOP", existing_id: targetId };
22
+ }
23
+ return { engram: deps.learn(statement, context), decision: "ADD" };
24
+ }
25
+ case "UPDATE": {
26
+ if (targetId) {
27
+ const existing = deps.getById(targetId);
28
+ if (existing && existing.commitment !== "locked") {
29
+ return withLock(deps.engramsPath, () => {
30
+ const engrams = loadEngrams(deps.engramsPath);
31
+ const idx = engrams.findIndex((e) => e.id === targetId);
32
+ if (idx === -1) return { engram: deps.learn(statement, context), decision: "ADD" };
33
+ const updated = { ...engrams[idx] };
34
+ updated.statement = statement;
35
+ updated.content_hash = computeContentHash(statement);
36
+ updated.version = (updated.version ?? 1) + 1;
37
+ updated.activation.last_accessed = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
38
+ if (context?.tags) updated.tags = [.../* @__PURE__ */ new Set([...updated.tags, ...context.tags])];
39
+ if (conflicts.length > 0) {
40
+ if (!updated.relations) updated.relations = { broader: [], narrower: [], related: [], conflicts: [] };
41
+ updated.relations.conflicts = [.../* @__PURE__ */ new Set([...updated.relations.conflicts ?? [], ...conflicts])];
42
+ }
43
+ engrams[idx] = updated;
44
+ saveEngrams(deps.engramsPath, engrams);
45
+ deps.syncIndex();
46
+ appendHistory(deps.rootPath, {
47
+ event: "engram_updated",
48
+ engram_id: targetId,
49
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
50
+ data: { old_statement: existing.statement, new_statement: statement, reason: "LLM dedup UPDATE" }
51
+ });
52
+ return {
53
+ engram: updated,
54
+ decision: "UPDATE",
55
+ existing_id: targetId,
56
+ tensions: conflicts.length > 0 ? conflicts : void 0
57
+ };
58
+ });
59
+ }
60
+ }
61
+ return { engram: deps.learn(statement, context), decision: "ADD" };
62
+ }
63
+ case "MERGE": {
64
+ if (targetId) {
65
+ const existing = deps.getById(targetId);
66
+ if (existing && existing.commitment !== "locked") {
67
+ return withLock(deps.engramsPath, () => {
68
+ const engrams = loadEngrams(deps.engramsPath);
69
+ const idx = engrams.findIndex((e) => e.id === targetId);
70
+ if (idx === -1) return { engram: deps.learn(statement, context), decision: "ADD" };
71
+ const merged = { ...engrams[idx] };
72
+ merged.statement = `${merged.statement} ${statement}`;
73
+ merged.content_hash = computeContentHash(merged.statement);
74
+ merged.version = (merged.version ?? 1) + 1;
75
+ merged.activation.last_accessed = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
76
+ if (context?.tags) merged.tags = [.../* @__PURE__ */ new Set([...merged.tags, ...context.tags])];
77
+ if (0.7 > merged.activation.retrieval_strength) merged.activation.retrieval_strength = 0.7;
78
+ if (conflicts.length > 0) {
79
+ if (!merged.relations) merged.relations = { broader: [], narrower: [], related: [], conflicts: [] };
80
+ merged.relations.conflicts = [.../* @__PURE__ */ new Set([...merged.relations.conflicts ?? [], ...conflicts])];
81
+ }
82
+ engrams[idx] = merged;
83
+ saveEngrams(deps.engramsPath, engrams);
84
+ deps.syncIndex();
85
+ appendHistory(deps.rootPath, {
86
+ event: "engram_merged",
87
+ engram_id: targetId,
88
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
89
+ data: { merged_statement: statement, reason: "LLM dedup MERGE" }
90
+ });
91
+ return {
92
+ engram: merged,
93
+ decision: "MERGE",
94
+ existing_id: targetId,
95
+ tensions: conflicts.length > 0 ? conflicts : void 0
96
+ };
97
+ });
98
+ }
99
+ }
100
+ return { engram: deps.learn(statement, context), decision: "ADD" };
101
+ }
102
+ case "ADD":
103
+ default: {
104
+ const engram = deps.learn(statement, context);
105
+ if (conflicts.length > 0) {
106
+ withLock(deps.engramsPath, () => {
107
+ const engrams = loadEngrams(deps.engramsPath);
108
+ const idx = engrams.findIndex((e) => e.id === engram.id);
109
+ if (idx !== -1) {
110
+ const updated = engrams[idx];
111
+ if (!updated.relations) updated.relations = { broader: [], narrower: [], related: [], conflicts: [] };
112
+ updated.relations.conflicts = [.../* @__PURE__ */ new Set([...updated.relations.conflicts ?? [], ...conflicts])];
113
+ engrams[idx] = updated;
114
+ saveEngrams(deps.engramsPath, engrams);
115
+ deps.syncIndex();
116
+ }
117
+ });
118
+ }
119
+ return { engram, decision: "ADD", tensions: conflicts.length > 0 ? conflicts : void 0 };
120
+ }
121
+ }
122
+ }
123
+ async function learnAsync(deps, statement, context) {
124
+ const hashMatch = deps.hashDedup(statement);
125
+ if (hashMatch) {
126
+ return { engram: hashMatch, decision: "NOOP", existing_id: hashMatch.id };
127
+ }
128
+ const { enabled = true, threshold = 0.85, mode = "llm" } = deps.dedupConfig;
129
+ if (!enabled || mode === "off") {
130
+ return { engram: deps.learn(statement, context), decision: "ADD" };
131
+ }
132
+ let candidates = [];
133
+ try {
134
+ candidates = await deps.recallHybrid(statement, { limit: 5 });
135
+ } catch {
136
+ candidates = deps.recall(statement, { limit: 5 });
137
+ }
138
+ candidates = candidates.filter((c) => c.status === "active");
139
+ if (candidates.length === 0) {
140
+ return { engram: deps.learn(statement, context), decision: "ADD" };
141
+ }
142
+ const llm = context?.llm;
143
+ let decision = "ADD";
144
+ let targetId = null;
145
+ let conflicts = [];
146
+ if (mode === "llm" && llm && deps.isLlmAvailable()) {
147
+ try {
148
+ const prompt = buildDedupPrompt(
149
+ statement,
150
+ candidates.map((c) => ({ id: c.id, statement: c.statement, type: c.type, domain: c.domain }))
151
+ );
152
+ const response = await llm(prompt);
153
+ const parsed = parseDedupResponse(response);
154
+ decision = parsed.decision;
155
+ targetId = parsed.target_id;
156
+ conflicts = parsed.conflicts;
157
+ deps.recordLlmSuccess();
158
+ } catch (err) {
159
+ logger.warning(`LLM dedup failed, falling back to cosine: ${err}`);
160
+ deps.recordLlmFailure();
161
+ decision = "ADD";
162
+ }
163
+ }
164
+ return executeDedupDecision(deps, statement, context, decision, targetId, conflicts);
165
+ }
166
+ async function learnBatch(deps, statements, llm) {
167
+ const results = [];
168
+ const stats = { added: 0, updated: 0, merged: 0, noops: 0 };
169
+ for (const { statement, context } of statements) {
170
+ const ctx = { ...context, llm: context?.llm ?? llm };
171
+ const result = await learnAsync(deps, statement, ctx);
172
+ results.push(result);
173
+ const key = result.decision.toLowerCase();
174
+ if (key === "noop") stats.noops++;
175
+ else if (key === "update") stats.updated++;
176
+ else if (key === "merge") stats.merged++;
177
+ else stats.added++;
178
+ }
179
+ return { results, stats };
180
+ }
181
+ export {
182
+ learnAsync,
183
+ learnBatch
184
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plur-ai/core",
3
- "version": "0.7.7",
3
+ "version": "0.8.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",