@plur-ai/core 0.9.3 → 0.9.5
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 +1 -1
- package/dist/{chunk-3ZPTRZE3.js → chunk-EQPTF4JZ.js} +11 -1
- package/dist/{chunk-IGGMRXAB.js → chunk-ZY4R3VEG.js} +52 -12
- package/dist/embeddings-3EXLC3EH.js +21 -0
- package/dist/index.d.ts +397 -13
- package/dist/index.js +556 -53
- package/dist/{learn-async-R5KZ5EWF.js → learn-async-IISWD7HC.js} +1 -2
- package/package.json +1 -1
- package/dist/chunk-2ZDO52B4.js +0 -52
- package/dist/embeddings-ZRT6IRPA.js +0 -14
- package/dist/onnxruntime_binding-5QEF3SUC.node +0 -0
- package/dist/onnxruntime_binding-BKPKNEGC.node +0 -0
- package/dist/onnxruntime_binding-FMOXGIUT.node +0 -0
- package/dist/onnxruntime_binding-OI2KMXC5.node +0 -0
- package/dist/onnxruntime_binding-UX44MLAZ.node +0 -0
- package/dist/onnxruntime_binding-Y2W7N7WY.node +0 -0
- package/dist/transformers.node-PH5YK5EA.js +0 -46777
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
computeIdf,
|
|
3
|
+
embedderStatus,
|
|
3
4
|
embeddingSearch,
|
|
4
5
|
embeddingSearchWithScores,
|
|
5
6
|
engramSearchText,
|
|
6
7
|
ftsScore,
|
|
7
8
|
ftsTokenize,
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
resetEmbedder,
|
|
10
|
+
searchEngrams,
|
|
11
|
+
setEmbeddingsEnabled
|
|
12
|
+
} from "./chunk-ZY4R3VEG.js";
|
|
10
13
|
import {
|
|
11
14
|
EngramSchemaPassthrough,
|
|
12
15
|
appendHistory,
|
|
@@ -26,19 +29,18 @@ import {
|
|
|
26
29
|
readHistoryForEngram,
|
|
27
30
|
saveEngrams,
|
|
28
31
|
storePrefix
|
|
29
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-EQPTF4JZ.js";
|
|
30
33
|
import {
|
|
31
34
|
atomicWrite,
|
|
32
35
|
getSyncStatus,
|
|
33
36
|
sync,
|
|
34
37
|
withLock
|
|
35
38
|
} from "./chunk-PRK3B7WR.js";
|
|
36
|
-
import "./chunk-2ZDO52B4.js";
|
|
37
39
|
|
|
38
40
|
// src/index.ts
|
|
39
41
|
import * as fs4 from "fs";
|
|
40
|
-
import
|
|
41
|
-
import
|
|
42
|
+
import { tmpdir } from "os";
|
|
43
|
+
import { join as join5, dirname as dirname2, basename as basename2 } from "path";
|
|
42
44
|
import yaml6 from "js-yaml";
|
|
43
45
|
|
|
44
46
|
// src/storage.ts
|
|
@@ -175,6 +177,7 @@ var IndexedStorage = class {
|
|
|
175
177
|
allSyncedIds.add(e.id);
|
|
176
178
|
}
|
|
177
179
|
for (const store of this.stores) {
|
|
180
|
+
if (!store.path) continue;
|
|
178
181
|
validSources.add(store.path);
|
|
179
182
|
const storeEngrams = loadEngrams(store.path);
|
|
180
183
|
const prefix = storePrefix(store.scope);
|
|
@@ -221,11 +224,17 @@ import yaml from "js-yaml";
|
|
|
221
224
|
// src/schemas/config.ts
|
|
222
225
|
import { z } from "zod";
|
|
223
226
|
var StoreEntrySchema = z.object({
|
|
224
|
-
path: z.string(),
|
|
227
|
+
path: z.string().optional(),
|
|
228
|
+
url: z.string().url().optional(),
|
|
229
|
+
token: z.string().optional(),
|
|
230
|
+
// Bearer for remote stores; ignored for path
|
|
225
231
|
scope: z.string(),
|
|
226
232
|
shared: z.boolean().default(false),
|
|
227
233
|
readonly: z.boolean().default(false)
|
|
228
|
-
})
|
|
234
|
+
}).refine(
|
|
235
|
+
(s) => Boolean(s.path) !== Boolean(s.url),
|
|
236
|
+
{ message: "StoreEntry requires exactly one of path or url" }
|
|
237
|
+
);
|
|
229
238
|
var LlmTierConfigSchema = z.object({
|
|
230
239
|
dedup_tier: z.enum(["fast", "balanced", "thorough"]).default("fast"),
|
|
231
240
|
profile_tier: z.enum(["fast", "balanced", "thorough"]).default("balanced"),
|
|
@@ -244,6 +253,9 @@ var StorageConfigSchema = z.object({
|
|
|
244
253
|
backend: z.enum(["yaml", "sqlite"]).default("yaml"),
|
|
245
254
|
path: z.string().optional()
|
|
246
255
|
}).partial();
|
|
256
|
+
var EmbeddingsConfigSchema = z.object({
|
|
257
|
+
enabled: z.boolean().default(true)
|
|
258
|
+
}).partial();
|
|
247
259
|
var PlurConfigSchema = z.object({
|
|
248
260
|
auto_learn: z.boolean().default(true),
|
|
249
261
|
auto_capture: z.boolean().default(true),
|
|
@@ -261,6 +273,7 @@ var PlurConfigSchema = z.object({
|
|
|
261
273
|
allow_secrets: z.boolean().default(false),
|
|
262
274
|
index: z.boolean().default(true),
|
|
263
275
|
storage: StorageConfigSchema.default({}),
|
|
276
|
+
embeddings: EmbeddingsConfigSchema.default({}),
|
|
264
277
|
stores: z.array(StoreEntrySchema).default([]),
|
|
265
278
|
llm: LlmTierConfigSchema.default({}),
|
|
266
279
|
profile: ProfileConfigSchema.default({}),
|
|
@@ -450,6 +463,7 @@ var DEFAULT_MAX_TOKENS = 8e3;
|
|
|
450
463
|
var DEFAULT_MIN_RELEVANCE = 0.3;
|
|
451
464
|
var MAX_PER_PACK = 5;
|
|
452
465
|
var MAX_PER_DOMAIN = 10;
|
|
466
|
+
var PINNED_TOKEN_BUDGET_RATIO = 0.5;
|
|
453
467
|
var DIP19_CONSIDER_MAX = 5;
|
|
454
468
|
var DIP19_CONSIDER_BUDGET = 200;
|
|
455
469
|
function getPackMetadata(manifest) {
|
|
@@ -528,7 +542,11 @@ function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilt
|
|
|
528
542
|
for (const word of promptWords) {
|
|
529
543
|
if (statementWords.has(word)) termHits += 0.5;
|
|
530
544
|
}
|
|
531
|
-
|
|
545
|
+
const isPinned = engram.pinned === true;
|
|
546
|
+
if (termHits === 0 && !isPinned) return 0;
|
|
547
|
+
if (termHits === 0 && isPinned) {
|
|
548
|
+
termHits = 0.5;
|
|
549
|
+
}
|
|
532
550
|
let rs = isPack ? engram.activation.retrieval_strength : decayedStrength(engram.activation.retrieval_strength, daysSince(engram.activation.last_accessed));
|
|
533
551
|
if (!isPack) {
|
|
534
552
|
const fb = engram.feedback_signals;
|
|
@@ -543,6 +561,7 @@ function scoreEngram(engram, promptLower, promptWords, packMatchTerms, scopeFilt
|
|
|
543
561
|
else if (netFeedback < 0) score *= Math.max(1 + netFeedback * 0.1, 0.5);
|
|
544
562
|
}
|
|
545
563
|
if (engram.consolidated) score *= 1.1;
|
|
564
|
+
if (isPinned) score *= 2;
|
|
546
565
|
const emotionalWeight = engram.episodic?.emotional_weight ?? 5;
|
|
547
566
|
score *= 1 + (emotionalWeight - 5) * 0.04;
|
|
548
567
|
return score;
|
|
@@ -552,7 +571,21 @@ function fillTokenBudget(scored, maxTokens) {
|
|
|
552
571
|
const packCounts = /* @__PURE__ */ new Map();
|
|
553
572
|
const domainCounts = /* @__PURE__ */ new Map();
|
|
554
573
|
let tokensUsed = 0;
|
|
555
|
-
|
|
574
|
+
const pinned = scored.filter((e) => e.pinned === true);
|
|
575
|
+
const unpinned = scored.filter((e) => e.pinned !== true);
|
|
576
|
+
const pinnedBudget = Math.floor(maxTokens * PINNED_TOKEN_BUDGET_RATIO);
|
|
577
|
+
for (const engram of pinned) {
|
|
578
|
+
const cost = estimateTokens(engram);
|
|
579
|
+
if (tokensUsed + cost > maxTokens) continue;
|
|
580
|
+
if (tokensUsed + cost > pinnedBudget) continue;
|
|
581
|
+
result.push(engram);
|
|
582
|
+
tokensUsed += cost;
|
|
583
|
+
const pack = engram.pack ?? "__personal__";
|
|
584
|
+
packCounts.set(pack, (packCounts.get(pack) ?? 0) + 1);
|
|
585
|
+
const topDomain = (engram.domain ?? "__none__").split(".")[0];
|
|
586
|
+
domainCounts.set(topDomain, (domainCounts.get(topDomain) ?? 0) + 1);
|
|
587
|
+
}
|
|
588
|
+
for (const engram of unpinned) {
|
|
556
589
|
const cost = estimateTokens(engram);
|
|
557
590
|
if (tokensUsed + cost > maxTokens) continue;
|
|
558
591
|
const pack = engram.pack ?? "__personal__";
|
|
@@ -586,7 +619,7 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
|
|
|
586
619
|
engramMap.set(engram.id, engram);
|
|
587
620
|
let raw = scoreEngram(engram, promptLower, promptWords, [], ctx.scope, false);
|
|
588
621
|
const embBoost = embeddingBoosts?.get(engram.id) ?? 0;
|
|
589
|
-
if (raw === 0 && embBoost > 0.
|
|
622
|
+
if (raw === 0 && embBoost > 0.5) {
|
|
590
623
|
raw = embBoost * 2;
|
|
591
624
|
} else if (raw > 0 && embBoost > 0) {
|
|
592
625
|
raw += embBoost;
|
|
@@ -611,7 +644,7 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
|
|
|
611
644
|
engramMap.set(engram.id, engram);
|
|
612
645
|
let raw = scoreEngram(engram, promptLower, promptWords, matchTerms, ctx.scope, true);
|
|
613
646
|
const embBoost = embeddingBoosts?.get(engram.id) ?? 0;
|
|
614
|
-
if (raw === 0 && embBoost > 0.
|
|
647
|
+
if (raw === 0 && embBoost > 0.5) {
|
|
615
648
|
raw = embBoost * 2;
|
|
616
649
|
} else if (raw > 0 && embBoost > 0) {
|
|
617
650
|
raw += embBoost;
|
|
@@ -631,7 +664,7 @@ function selectAndSpread(ctx, personalEngrams, packs, config, embeddingBoosts) {
|
|
|
631
664
|
aBoosts.set(e.id, aBoost);
|
|
632
665
|
e.score = e.keyword_match + aBoost;
|
|
633
666
|
}
|
|
634
|
-
const filtered = scored.filter((s) => s.score >= minRelevance);
|
|
667
|
+
const filtered = scored.filter((s) => s.pinned === true || s.score >= minRelevance);
|
|
635
668
|
filtered.sort((a, b) => b.score - a.score);
|
|
636
669
|
const { selected: directives, tokens_used: directiveTokens } = fillTokenBudget(filtered, maxTokens);
|
|
637
670
|
const directiveIds = new Set(directives.map((e) => e.id));
|
|
@@ -772,8 +805,8 @@ function generateEpisodeId() {
|
|
|
772
805
|
const rand = Math.random().toString(36).slice(2, 6);
|
|
773
806
|
return `EP-${ts}-${rand}`;
|
|
774
807
|
}
|
|
775
|
-
function captureEpisode(
|
|
776
|
-
const episodes = loadEpisodes(
|
|
808
|
+
function captureEpisode(path3, summary, context) {
|
|
809
|
+
const episodes = loadEpisodes(path3);
|
|
777
810
|
const episode = {
|
|
778
811
|
id: generateEpisodeId(),
|
|
779
812
|
summary,
|
|
@@ -784,11 +817,11 @@ function captureEpisode(path4, summary, context) {
|
|
|
784
817
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
785
818
|
};
|
|
786
819
|
episodes.push(episode);
|
|
787
|
-
atomicWrite(
|
|
820
|
+
atomicWrite(path3, yaml2.dump(episodes, { lineWidth: 120, noRefs: true }));
|
|
788
821
|
return episode;
|
|
789
822
|
}
|
|
790
|
-
function queryTimeline(
|
|
791
|
-
let episodes = loadEpisodes(
|
|
823
|
+
function queryTimeline(path3, query) {
|
|
824
|
+
let episodes = loadEpisodes(path3);
|
|
792
825
|
if (query?.since) episodes = episodes.filter((e) => new Date(e.timestamp) >= query.since);
|
|
793
826
|
if (query?.until) episodes = episodes.filter((e) => new Date(e.timestamp) <= query.until);
|
|
794
827
|
if (query?.agent) episodes = episodes.filter((e) => e.agent === query.agent);
|
|
@@ -799,10 +832,10 @@ function queryTimeline(path4, query) {
|
|
|
799
832
|
}
|
|
800
833
|
return episodes;
|
|
801
834
|
}
|
|
802
|
-
function loadEpisodes(
|
|
803
|
-
if (!existsSync4(
|
|
835
|
+
function loadEpisodes(path3) {
|
|
836
|
+
if (!existsSync4(path3)) return [];
|
|
804
837
|
try {
|
|
805
|
-
const raw = yaml2.load(readFileSync2(
|
|
838
|
+
const raw = yaml2.load(readFileSync2(path3, "utf8"));
|
|
806
839
|
return Array.isArray(raw) ? raw : [];
|
|
807
840
|
} catch {
|
|
808
841
|
return [];
|
|
@@ -903,7 +936,13 @@ function isAggregationQuery(query) {
|
|
|
903
936
|
return AGGREGATION_PATTERNS.some((p) => p.test(query));
|
|
904
937
|
}
|
|
905
938
|
async function hybridSearch(engrams, query, limit, storagePath) {
|
|
906
|
-
|
|
939
|
+
const result = await hybridSearchWithMeta(engrams, query, limit, storagePath);
|
|
940
|
+
return result.engrams;
|
|
941
|
+
}
|
|
942
|
+
async function hybridSearchWithMeta(engrams, query, limit, storagePath) {
|
|
943
|
+
if (engrams.length === 0) {
|
|
944
|
+
return { engrams: [], mode: "hybrid", embedderError: null };
|
|
945
|
+
}
|
|
907
946
|
const exhaustive = isAggregationQuery(query);
|
|
908
947
|
const effectiveLimit = exhaustive ? Math.max(limit, 50) : limit;
|
|
909
948
|
const bm25Limit = Math.min(engrams.length, exhaustive ? effectiveLimit * 5 : effectiveLimit * 3);
|
|
@@ -912,8 +951,23 @@ async function hybridSearch(engrams, query, limit, storagePath) {
|
|
|
912
951
|
Promise.resolve(searchEngrams(engrams, query, bm25Limit)),
|
|
913
952
|
embeddingSearch(engrams, query, embLimit, storagePath)
|
|
914
953
|
]);
|
|
954
|
+
const status = embedderStatus();
|
|
955
|
+
let mode;
|
|
956
|
+
let embedderError = null;
|
|
957
|
+
if (status.disabled) {
|
|
958
|
+
mode = "bm25-only";
|
|
959
|
+
} else if (!status.available || embResults.length === 0 && !!status.lastError) {
|
|
960
|
+
mode = "hybrid-degraded";
|
|
961
|
+
embedderError = status.lastError;
|
|
962
|
+
} else {
|
|
963
|
+
mode = "hybrid";
|
|
964
|
+
}
|
|
915
965
|
const merged = rrfMerge([bm25Results, embResults]);
|
|
916
|
-
return
|
|
966
|
+
return {
|
|
967
|
+
engrams: merged.slice(0, effectiveLimit),
|
|
968
|
+
mode,
|
|
969
|
+
embedderError
|
|
970
|
+
};
|
|
917
971
|
}
|
|
918
972
|
|
|
919
973
|
// src/query-expansion.ts
|
|
@@ -1469,6 +1523,137 @@ function computePackHash(packDir) {
|
|
|
1469
1523
|
return hash.digest("hex");
|
|
1470
1524
|
}
|
|
1471
1525
|
|
|
1526
|
+
// src/store/remote-store.ts
|
|
1527
|
+
var RemoteStore = class {
|
|
1528
|
+
constructor(url, token, scope, opts = {}) {
|
|
1529
|
+
this.url = url;
|
|
1530
|
+
this.token = token;
|
|
1531
|
+
this.scope = scope;
|
|
1532
|
+
this.opts = opts;
|
|
1533
|
+
}
|
|
1534
|
+
cache = null;
|
|
1535
|
+
inFlight = null;
|
|
1536
|
+
get apiBase() {
|
|
1537
|
+
return this.url.replace(/\/sse\/?$/, "").replace(/\/$/, "") + "/api/v1";
|
|
1538
|
+
}
|
|
1539
|
+
get ttlMs() {
|
|
1540
|
+
return this.opts.ttlMs ?? 6e4;
|
|
1541
|
+
}
|
|
1542
|
+
headers(extra = {}) {
|
|
1543
|
+
return {
|
|
1544
|
+
Authorization: `Bearer ${this.token}`,
|
|
1545
|
+
Accept: "application/json",
|
|
1546
|
+
...extra
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
/**
|
|
1550
|
+
* Load all engrams visible to this token at this scope. Cached up to
|
|
1551
|
+
* ttlMs; in-flight calls deduplicate to avoid thundering-herd on
|
|
1552
|
+
* the remote when 5 things ask for engrams at once.
|
|
1553
|
+
*/
|
|
1554
|
+
async load() {
|
|
1555
|
+
const now = Date.now();
|
|
1556
|
+
if (this.cache && now - this.cache.ts < this.ttlMs) return this.cache.engrams;
|
|
1557
|
+
if (this.inFlight) return this.inFlight;
|
|
1558
|
+
this.inFlight = (async () => {
|
|
1559
|
+
try {
|
|
1560
|
+
const all = [];
|
|
1561
|
+
let offset = 0;
|
|
1562
|
+
const limit = 200;
|
|
1563
|
+
const maxPages = 50;
|
|
1564
|
+
for (let i = 0; i < maxPages; i++) {
|
|
1565
|
+
const u = `${this.apiBase}/engrams?scope=${encodeURIComponent(this.scope)}&limit=${limit}&offset=${offset}`;
|
|
1566
|
+
const r = await fetch(u, { headers: this.headers() });
|
|
1567
|
+
if (!r.ok) {
|
|
1568
|
+
if (r.status >= 500) console.error(`[plur:remote-store] ${this.url} returned ${r.status} loading scope ${this.scope}`);
|
|
1569
|
+
break;
|
|
1570
|
+
}
|
|
1571
|
+
const body = await r.json();
|
|
1572
|
+
for (const row of body.rows) {
|
|
1573
|
+
const d = row.data ?? {};
|
|
1574
|
+
all.push({
|
|
1575
|
+
id: row.id,
|
|
1576
|
+
scope: row.scope,
|
|
1577
|
+
status: row.status,
|
|
1578
|
+
...d
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
if (all.length >= body.total_count || body.rows.length < limit) break;
|
|
1582
|
+
offset += limit;
|
|
1583
|
+
}
|
|
1584
|
+
this.cache = { ts: Date.now(), engrams: all };
|
|
1585
|
+
return all;
|
|
1586
|
+
} catch (err) {
|
|
1587
|
+
console.error(`[plur:remote-store] ${this.url} load failed: ${err.message}`);
|
|
1588
|
+
return this.cache?.engrams ?? [];
|
|
1589
|
+
} finally {
|
|
1590
|
+
this.inFlight = null;
|
|
1591
|
+
}
|
|
1592
|
+
})();
|
|
1593
|
+
return this.inFlight;
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Append a single engram to the remote store. POST /api/v1/engrams
|
|
1597
|
+
* carries statement + scope + domain + type — the server handles
|
|
1598
|
+
* ID assignment, content_hash, status.
|
|
1599
|
+
*/
|
|
1600
|
+
async append(engram) {
|
|
1601
|
+
const body = JSON.stringify({
|
|
1602
|
+
statement: engram.statement,
|
|
1603
|
+
scope: engram.scope,
|
|
1604
|
+
domain: engram.domain,
|
|
1605
|
+
type: engram.type
|
|
1606
|
+
});
|
|
1607
|
+
const r = await fetch(`${this.apiBase}/engrams`, {
|
|
1608
|
+
method: "POST",
|
|
1609
|
+
headers: this.headers({ "Content-Type": "application/json" }),
|
|
1610
|
+
body
|
|
1611
|
+
});
|
|
1612
|
+
if (!r.ok) {
|
|
1613
|
+
const text = await r.text().catch(() => "");
|
|
1614
|
+
throw new Error(`Remote store append failed: ${r.status} ${text}`);
|
|
1615
|
+
}
|
|
1616
|
+
this.cache = null;
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* `save(all)` — used by migrations to bulk-replace. Not supported
|
|
1620
|
+
* on remote: the server keeps an audit trail and we don't want a
|
|
1621
|
+
* single client to be able to nuke + replace the whole store. Throws.
|
|
1622
|
+
*/
|
|
1623
|
+
async save(_engrams) {
|
|
1624
|
+
throw new Error("Remote store does not support bulk save() \u2014 use append()/remove() per engram");
|
|
1625
|
+
}
|
|
1626
|
+
async getById(id) {
|
|
1627
|
+
try {
|
|
1628
|
+
const r = await fetch(`${this.apiBase}/engrams/${encodeURIComponent(id)}`, { headers: this.headers() });
|
|
1629
|
+
if (r.status === 404) return null;
|
|
1630
|
+
if (!r.ok) return null;
|
|
1631
|
+
const row = await r.json();
|
|
1632
|
+
return { id: row.id, scope: row.scope, status: row.status, ...row.data ?? {} };
|
|
1633
|
+
} catch {
|
|
1634
|
+
return null;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
/** Remove → DELETE /api/v1/engrams/:id (server soft-retires). */
|
|
1638
|
+
async remove(id) {
|
|
1639
|
+
const r = await fetch(`${this.apiBase}/engrams/${encodeURIComponent(id)}`, {
|
|
1640
|
+
method: "DELETE",
|
|
1641
|
+
headers: this.headers()
|
|
1642
|
+
});
|
|
1643
|
+
if (!r.ok) return false;
|
|
1644
|
+
this.cache = null;
|
|
1645
|
+
return true;
|
|
1646
|
+
}
|
|
1647
|
+
async count(filter) {
|
|
1648
|
+
const all = await this.load();
|
|
1649
|
+
if (filter?.status) return all.filter((e) => e.status === filter.status).length;
|
|
1650
|
+
return all.length;
|
|
1651
|
+
}
|
|
1652
|
+
async close() {
|
|
1653
|
+
this.cache = null;
|
|
1654
|
+
}
|
|
1655
|
+
};
|
|
1656
|
+
|
|
1472
1657
|
// src/meta/sanitize.ts
|
|
1473
1658
|
function sanitizeForPrompt(text) {
|
|
1474
1659
|
return text.replace(/```/g, "~~~").replace(/\n{3,}/g, "\n\n").replace(/^(system|assistant|user):/gim, "$1 -").slice(0, 2e3);
|
|
@@ -1585,7 +1770,7 @@ async function computeSimilarityMatrix(templates) {
|
|
|
1585
1770
|
const n = templates.length;
|
|
1586
1771
|
const matrix = Array.from({ length: n }, () => Array(n).fill(0));
|
|
1587
1772
|
try {
|
|
1588
|
-
const { embed, cosineSimilarity } = await import("./embeddings-
|
|
1773
|
+
const { embed, cosineSimilarity } = await import("./embeddings-3EXLC3EH.js");
|
|
1589
1774
|
const embeddings = [];
|
|
1590
1775
|
for (const t of templates) {
|
|
1591
1776
|
embeddings.push(await embed(t));
|
|
@@ -2936,6 +3121,192 @@ function isNewer(a, b) {
|
|
|
2936
3121
|
return false;
|
|
2937
3122
|
}
|
|
2938
3123
|
|
|
3124
|
+
// src/schemas/capsule.ts
|
|
3125
|
+
import { z as z3 } from "zod";
|
|
3126
|
+
var CAPSULE_MAGIC = Buffer.from([80, 76, 85, 82]);
|
|
3127
|
+
var CAPSULE_MAGIC_HEX = "50 4c 55 52";
|
|
3128
|
+
var FORMAT_VERSION_V1 = 1;
|
|
3129
|
+
var SUPPORTED_FORMAT_VERSIONS = [FORMAT_VERSION_V1];
|
|
3130
|
+
var CAPSULE_FLAGS = {
|
|
3131
|
+
SIGNED: 1 << 0,
|
|
3132
|
+
COMPRESSED: 1 << 1
|
|
3133
|
+
};
|
|
3134
|
+
var CAPSULE_FLAG_RESERVED_MASK = 65532;
|
|
3135
|
+
var PREAMBLE_LEN = 12;
|
|
3136
|
+
var CAPSULE_SIZE_LIMITS = {
|
|
3137
|
+
SOFT_BYTES: 100 * 1024 * 1024,
|
|
3138
|
+
HARD_BYTES: 1024 * 1024 * 1024
|
|
3139
|
+
};
|
|
3140
|
+
var ED25519_SIG_LEN = 64;
|
|
3141
|
+
var ManifestSummarySchema = z3.object({
|
|
3142
|
+
name: z3.string().min(1),
|
|
3143
|
+
version: z3.string().min(1),
|
|
3144
|
+
creator: z3.string().optional(),
|
|
3145
|
+
engram_count: z3.number().int().min(0),
|
|
3146
|
+
domain: z3.string().optional(),
|
|
3147
|
+
license: z3.string().default("cc-by-sa-4.0")
|
|
3148
|
+
});
|
|
3149
|
+
var PayloadDescriptorSchema = z3.object({
|
|
3150
|
+
compression: z3.enum(["gzip", "none"]),
|
|
3151
|
+
size_compressed: z3.number().int().min(0),
|
|
3152
|
+
size_uncompressed: z3.number().int().min(0),
|
|
3153
|
+
sha256: z3.string().regex(/^[0-9a-f]{64}$/, "sha256 must be 64 lowercase hex chars")
|
|
3154
|
+
});
|
|
3155
|
+
var ProducerSchema = z3.object({
|
|
3156
|
+
tool: z3.string().min(1),
|
|
3157
|
+
version: z3.string().min(1),
|
|
3158
|
+
agent_id: z3.string().optional()
|
|
3159
|
+
});
|
|
3160
|
+
var SignerSchema = z3.object({
|
|
3161
|
+
algo: z3.literal("ed25519"),
|
|
3162
|
+
public_key: z3.string().min(1),
|
|
3163
|
+
key_id: z3.string().optional()
|
|
3164
|
+
});
|
|
3165
|
+
var CapsuleHeaderSchema = z3.object({
|
|
3166
|
+
schema: z3.literal("plur.capsule/1"),
|
|
3167
|
+
product_type: z3.enum(["engram-pack", "skill"]),
|
|
3168
|
+
manifest_summary: ManifestSummarySchema,
|
|
3169
|
+
payload: PayloadDescriptorSchema,
|
|
3170
|
+
created_at: z3.string().datetime({ offset: true }),
|
|
3171
|
+
producer: ProducerSchema,
|
|
3172
|
+
signer: SignerSchema.nullable().default(null)
|
|
3173
|
+
});
|
|
3174
|
+
function parseCapsulePreamble(buf) {
|
|
3175
|
+
if (buf.length < PREAMBLE_LEN) {
|
|
3176
|
+
throw new Error(`capsule: truncated preamble (got ${buf.length} bytes, need ${PREAMBLE_LEN})`);
|
|
3177
|
+
}
|
|
3178
|
+
if (buf.compare(CAPSULE_MAGIC, 0, 4, 0, 4) !== 0) {
|
|
3179
|
+
throw new Error(`capsule: bad magic (expected ${CAPSULE_MAGIC_HEX})`);
|
|
3180
|
+
}
|
|
3181
|
+
const formatVersion = buf.readUInt16LE(4);
|
|
3182
|
+
if (!SUPPORTED_FORMAT_VERSIONS.includes(formatVersion)) {
|
|
3183
|
+
throw new Error(`capsule: unsupported FormatVersion 0x${formatVersion.toString(16).padStart(4, "0")}`);
|
|
3184
|
+
}
|
|
3185
|
+
const flags = buf.readUInt16LE(6);
|
|
3186
|
+
if ((flags & CAPSULE_FLAG_RESERVED_MASK) !== 0) {
|
|
3187
|
+
throw new Error(`capsule: reserved flag bits set (flags=0x${flags.toString(16).padStart(4, "0")})`);
|
|
3188
|
+
}
|
|
3189
|
+
const headerLen = buf.readUInt32LE(8);
|
|
3190
|
+
if (headerLen === 0) {
|
|
3191
|
+
throw new Error("capsule: HeaderLen must be > 0");
|
|
3192
|
+
}
|
|
3193
|
+
if (headerLen > CAPSULE_SIZE_LIMITS.HARD_BYTES) {
|
|
3194
|
+
throw new Error(`capsule: HeaderLen ${headerLen} exceeds hard size limit`);
|
|
3195
|
+
}
|
|
3196
|
+
return { formatVersion, flags, headerLen };
|
|
3197
|
+
}
|
|
3198
|
+
function serializeCapsulePreamble(p) {
|
|
3199
|
+
const buf = Buffer.alloc(PREAMBLE_LEN);
|
|
3200
|
+
CAPSULE_MAGIC.copy(buf, 0);
|
|
3201
|
+
buf.writeUInt16LE(p.formatVersion, 4);
|
|
3202
|
+
buf.writeUInt16LE(p.flags, 6);
|
|
3203
|
+
buf.writeUInt32LE(p.headerLen, 8);
|
|
3204
|
+
return buf;
|
|
3205
|
+
}
|
|
3206
|
+
function hasFlag(flags, flag) {
|
|
3207
|
+
return (flags & flag) === flag;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
// src/capsule.ts
|
|
3211
|
+
import { createHash as createHash2 } from "crypto";
|
|
3212
|
+
function writeCapsule(opts) {
|
|
3213
|
+
const compression = opts.compression ?? "gzip";
|
|
3214
|
+
const productType = opts.productType ?? "engram-pack";
|
|
3215
|
+
const createdAt = opts.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
3216
|
+
const signer = opts.signer ?? null;
|
|
3217
|
+
if (signer !== null && (!opts.signature || opts.signature.length !== ED25519_SIG_LEN)) {
|
|
3218
|
+
throw new Error(`writeCapsule: signer set but signature missing or not ${ED25519_SIG_LEN} bytes`);
|
|
3219
|
+
}
|
|
3220
|
+
if (signer === null && opts.signature) {
|
|
3221
|
+
throw new Error("writeCapsule: signature provided without signer \u2014 refuse ambiguous envelope");
|
|
3222
|
+
}
|
|
3223
|
+
const sha256 = createHash2("sha256").update(opts.payload).digest("hex");
|
|
3224
|
+
const sizeCompressed = opts.payload.length;
|
|
3225
|
+
const sizeUncompressed = opts.sizeUncompressed ?? sizeCompressed;
|
|
3226
|
+
const header = CapsuleHeaderSchema.parse({
|
|
3227
|
+
schema: "plur.capsule/1",
|
|
3228
|
+
product_type: productType,
|
|
3229
|
+
manifest_summary: opts.manifestSummary,
|
|
3230
|
+
payload: {
|
|
3231
|
+
compression,
|
|
3232
|
+
size_compressed: sizeCompressed,
|
|
3233
|
+
size_uncompressed: sizeUncompressed,
|
|
3234
|
+
sha256
|
|
3235
|
+
},
|
|
3236
|
+
created_at: createdAt,
|
|
3237
|
+
producer: opts.producer,
|
|
3238
|
+
signer
|
|
3239
|
+
});
|
|
3240
|
+
const headerJson = Buffer.from(JSON.stringify(header), "utf-8");
|
|
3241
|
+
let flags = 0;
|
|
3242
|
+
if (compression === "gzip") flags |= CAPSULE_FLAGS.COMPRESSED;
|
|
3243
|
+
if (signer !== null) flags |= CAPSULE_FLAGS.SIGNED;
|
|
3244
|
+
const preamble = serializeCapsulePreamble({
|
|
3245
|
+
formatVersion: FORMAT_VERSION_V1,
|
|
3246
|
+
flags,
|
|
3247
|
+
headerLen: headerJson.length
|
|
3248
|
+
});
|
|
3249
|
+
const totalLen = PREAMBLE_LEN + headerJson.length + opts.payload.length + (signer !== null ? ED25519_SIG_LEN : 0);
|
|
3250
|
+
if (totalLen > CAPSULE_SIZE_LIMITS.HARD_BYTES) {
|
|
3251
|
+
throw new Error(`writeCapsule: capsule size ${totalLen} exceeds hard limit`);
|
|
3252
|
+
}
|
|
3253
|
+
const parts = [preamble, headerJson, opts.payload];
|
|
3254
|
+
if (signer !== null && opts.signature) parts.push(opts.signature);
|
|
3255
|
+
return Buffer.concat(parts, totalLen);
|
|
3256
|
+
}
|
|
3257
|
+
function readCapsule(buf) {
|
|
3258
|
+
if (buf.length > CAPSULE_SIZE_LIMITS.HARD_BYTES) {
|
|
3259
|
+
throw new Error(`readCapsule: capsule size ${buf.length} exceeds hard limit`);
|
|
3260
|
+
}
|
|
3261
|
+
const preamble = parseCapsulePreamble(buf);
|
|
3262
|
+
const headerStart = PREAMBLE_LEN;
|
|
3263
|
+
const headerEnd = headerStart + preamble.headerLen;
|
|
3264
|
+
if (buf.length < headerEnd) {
|
|
3265
|
+
throw new Error(`readCapsule: truncated header (need ${headerEnd} bytes, got ${buf.length})`);
|
|
3266
|
+
}
|
|
3267
|
+
const headerJson = buf.subarray(headerStart, headerEnd).toString("utf-8");
|
|
3268
|
+
let parsedHeader;
|
|
3269
|
+
try {
|
|
3270
|
+
parsedHeader = JSON.parse(headerJson);
|
|
3271
|
+
} catch (err) {
|
|
3272
|
+
throw new Error(`readCapsule: malformed header JSON \u2014 ${err.message}`);
|
|
3273
|
+
}
|
|
3274
|
+
const header = CapsuleHeaderSchema.parse(parsedHeader);
|
|
3275
|
+
const isSigned = hasFlag(preamble.flags, CAPSULE_FLAGS.SIGNED);
|
|
3276
|
+
const sigLen = isSigned ? ED25519_SIG_LEN : 0;
|
|
3277
|
+
const payloadEnd = buf.length - sigLen;
|
|
3278
|
+
if (payloadEnd < headerEnd) {
|
|
3279
|
+
throw new Error("readCapsule: payload region underflow");
|
|
3280
|
+
}
|
|
3281
|
+
const payload = buf.subarray(headerEnd, payloadEnd);
|
|
3282
|
+
const signature = isSigned ? buf.subarray(payloadEnd) : null;
|
|
3283
|
+
if (payload.length !== header.payload.size_compressed) {
|
|
3284
|
+
throw new Error(
|
|
3285
|
+
`readCapsule: payload size mismatch (header=${header.payload.size_compressed}, actual=${payload.length})`
|
|
3286
|
+
);
|
|
3287
|
+
}
|
|
3288
|
+
const actualSha = createHash2("sha256").update(payload).digest("hex");
|
|
3289
|
+
if (actualSha !== header.payload.sha256) {
|
|
3290
|
+
throw new Error(`readCapsule: integrity mismatch (header=${header.payload.sha256}, actual=${actualSha})`);
|
|
3291
|
+
}
|
|
3292
|
+
const compressionFlagSet = hasFlag(preamble.flags, CAPSULE_FLAGS.COMPRESSED);
|
|
3293
|
+
const headerSaysCompressed = header.payload.compression === "gzip";
|
|
3294
|
+
if (compressionFlagSet !== headerSaysCompressed) {
|
|
3295
|
+
throw new Error(
|
|
3296
|
+
`readCapsule: COMPRESSED flag (${compressionFlagSet}) disagrees with header.payload.compression (${header.payload.compression})`
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
return { header, payload: Buffer.from(payload), signature: signature ? Buffer.from(signature) : null };
|
|
3300
|
+
}
|
|
3301
|
+
function verifyCapsuleIntegrity(buf) {
|
|
3302
|
+
try {
|
|
3303
|
+
readCapsule(buf);
|
|
3304
|
+
return true;
|
|
3305
|
+
} catch {
|
|
3306
|
+
return false;
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
|
|
2939
3310
|
// src/index.ts
|
|
2940
3311
|
var COMMITMENT_MULTIPLIER = {
|
|
2941
3312
|
locked: 1,
|
|
@@ -2973,6 +3344,9 @@ var Plur = class {
|
|
|
2973
3344
|
if (this.config.index) {
|
|
2974
3345
|
this.indexedStorage = new IndexedStorage(this.paths.engrams, this.paths.db, this.config.stores);
|
|
2975
3346
|
}
|
|
3347
|
+
if (this.config.embeddings?.enabled === false) {
|
|
3348
|
+
setEmbeddingsEnabled(false, "embeddings disabled in config.yaml (embeddings.enabled = false)");
|
|
3349
|
+
}
|
|
2976
3350
|
}
|
|
2977
3351
|
/**
|
|
2978
3352
|
* Load engrams from primary store + all configured stores, with mtime-based caching.
|
|
@@ -2984,7 +3358,7 @@ var Plur = class {
|
|
|
2984
3358
|
const stores = this.config.stores ?? [];
|
|
2985
3359
|
const all = [...primary];
|
|
2986
3360
|
for (const store of stores) {
|
|
2987
|
-
const storeEngrams = this._loadCached(store.path);
|
|
3361
|
+
const storeEngrams = store.url ? this._loadRemoteCached(store) : this._loadCached(store.path);
|
|
2988
3362
|
const prefix = storePrefix(store.scope);
|
|
2989
3363
|
for (const e of storeEngrams) {
|
|
2990
3364
|
if (e.scope !== "global" && e.scope !== store.scope && !e.scope.startsWith(store.scope)) {
|
|
@@ -3014,19 +3388,45 @@ var Plur = class {
|
|
|
3014
3388
|
return all;
|
|
3015
3389
|
}
|
|
3016
3390
|
/** Load engrams from a path with mtime-based caching */
|
|
3017
|
-
_loadCached(
|
|
3391
|
+
_loadCached(path3) {
|
|
3018
3392
|
let mtime;
|
|
3019
3393
|
try {
|
|
3020
|
-
mtime = fs4.statSync(
|
|
3394
|
+
mtime = fs4.statSync(path3, { bigint: true }).mtimeNs;
|
|
3021
3395
|
} catch {
|
|
3022
3396
|
return [];
|
|
3023
3397
|
}
|
|
3024
|
-
const cached = this._engramCache.get(
|
|
3398
|
+
const cached = this._engramCache.get(path3);
|
|
3025
3399
|
if (cached && cached.mtime === mtime) return cached.engrams;
|
|
3026
|
-
const engrams = loadEngrams(
|
|
3027
|
-
this._engramCache.set(
|
|
3400
|
+
const engrams = loadEngrams(path3);
|
|
3401
|
+
this._engramCache.set(path3, { mtime, engrams });
|
|
3028
3402
|
return engrams;
|
|
3029
3403
|
}
|
|
3404
|
+
/**
|
|
3405
|
+
* Per-instance pool of RemoteStore drivers, keyed by url+scope.
|
|
3406
|
+
* RemoteStore holds its own internal TTL cache so repeated load()
|
|
3407
|
+
* within ttlMs returns the same array without a network call.
|
|
3408
|
+
*
|
|
3409
|
+
* Note `_loadAllEngrams` is sync but RemoteStore.load() is async.
|
|
3410
|
+
* We bridge that by returning whatever's in the driver's cache
|
|
3411
|
+
* synchronously and triggering a background refresh on cache miss.
|
|
3412
|
+
* The first call after server start returns [] for that store; the
|
|
3413
|
+
* call after the first refresh sees the data. For our pilot this
|
|
3414
|
+
* is acceptable — recall is expected to be tried more than once
|
|
3415
|
+
* in any real session.
|
|
3416
|
+
*/
|
|
3417
|
+
_remoteStores = /* @__PURE__ */ new Map();
|
|
3418
|
+
_loadRemoteCached(store) {
|
|
3419
|
+
const key = `${store.url}::${store.scope}`;
|
|
3420
|
+
let driver = this._remoteStores.get(key);
|
|
3421
|
+
if (!driver) {
|
|
3422
|
+
driver = new RemoteStore(store.url, store.token ?? "", store.scope);
|
|
3423
|
+
this._remoteStores.set(key, driver);
|
|
3424
|
+
}
|
|
3425
|
+
const cached = driver.cache;
|
|
3426
|
+
void driver.load().catch(() => {
|
|
3427
|
+
});
|
|
3428
|
+
return cached?.engrams ?? [];
|
|
3429
|
+
}
|
|
3030
3430
|
/**
|
|
3031
3431
|
* Write engrams to disk and invalidate the cache for that path.
|
|
3032
3432
|
*
|
|
@@ -3038,9 +3438,9 @@ var Plur = class {
|
|
|
3038
3438
|
* invalidation on write removes the filesystem as a source of cache
|
|
3039
3439
|
* freshness and closes the race. See issue #25.
|
|
3040
3440
|
*/
|
|
3041
|
-
_writeEngrams(
|
|
3042
|
-
saveEngrams(
|
|
3043
|
-
this._engramCache.delete(
|
|
3441
|
+
_writeEngrams(path3, engrams) {
|
|
3442
|
+
saveEngrams(path3, engrams);
|
|
3443
|
+
this._engramCache.delete(path3);
|
|
3044
3444
|
}
|
|
3045
3445
|
/** Find which store owns an engram by ID. For namespaced IDs, strips prefix to find in store. */
|
|
3046
3446
|
_findEngramStore(id) {
|
|
@@ -3050,6 +3450,7 @@ var Plur = class {
|
|
|
3050
3450
|
}
|
|
3051
3451
|
const stores = this.config.stores ?? [];
|
|
3052
3452
|
for (const store of stores) {
|
|
3453
|
+
if (!store.path) continue;
|
|
3053
3454
|
const prefix = storePrefix(store.scope);
|
|
3054
3455
|
const nsPattern = new RegExp(`^(ENG|ABS|META)-${prefix}-`);
|
|
3055
3456
|
if (nsPattern.test(id)) {
|
|
@@ -3168,7 +3569,8 @@ var Plur = class {
|
|
|
3168
3569
|
narrower: [],
|
|
3169
3570
|
related: [],
|
|
3170
3571
|
conflicts: conflictIds
|
|
3171
|
-
} : void 0
|
|
3572
|
+
} : void 0,
|
|
3573
|
+
pinned: context?.pinned === true ? true : void 0
|
|
3172
3574
|
};
|
|
3173
3575
|
engrams.push(engram);
|
|
3174
3576
|
this._writeEngrams(this.paths.engrams, engrams);
|
|
@@ -3201,12 +3603,12 @@ var Plur = class {
|
|
|
3201
3603
|
}
|
|
3202
3604
|
/** Async learn with LLM-driven deduplication (Ideas 1+2+19). */
|
|
3203
3605
|
async learnAsync(statement, context) {
|
|
3204
|
-
const { learnAsync: learnAsyncImpl } = await import("./learn-async-
|
|
3606
|
+
const { learnAsync: learnAsyncImpl } = await import("./learn-async-IISWD7HC.js");
|
|
3205
3607
|
return learnAsyncImpl(this._learnAsyncDeps(), statement, context);
|
|
3206
3608
|
}
|
|
3207
3609
|
/** Batch learn with LLM dedup. */
|
|
3208
3610
|
async learnBatch(statements, llm) {
|
|
3209
|
-
const { learnBatch: learnBatchImpl } = await import("./learn-async-
|
|
3611
|
+
const { learnBatch: learnBatchImpl } = await import("./learn-async-IISWD7HC.js");
|
|
3210
3612
|
return learnBatchImpl(this._learnAsyncDeps(), statements, llm);
|
|
3211
3613
|
}
|
|
3212
3614
|
/**
|
|
@@ -3247,6 +3649,26 @@ var Plur = class {
|
|
|
3247
3649
|
this._reactivateResults(results);
|
|
3248
3650
|
return results;
|
|
3249
3651
|
}
|
|
3652
|
+
/**
|
|
3653
|
+
* Hybrid search with diagnostic metadata — returns both the engrams and
|
|
3654
|
+
* whether embeddings actually contributed (mode: "hybrid" vs "hybrid-degraded").
|
|
3655
|
+
* Use this when you want to surface degraded-mode warnings to users.
|
|
3656
|
+
*/
|
|
3657
|
+
async recallHybridWithMeta(query, options) {
|
|
3658
|
+
const filtered = this._filterEngrams(options);
|
|
3659
|
+
const limit = options?.limit ?? 20;
|
|
3660
|
+
const result = await hybridSearchWithMeta(filtered, query, limit, this.paths.root);
|
|
3661
|
+
this._reactivateResults(result.engrams);
|
|
3662
|
+
return result;
|
|
3663
|
+
}
|
|
3664
|
+
/** Inspect embedder availability without forcing a load. */
|
|
3665
|
+
embedderStatus() {
|
|
3666
|
+
return embedderStatus();
|
|
3667
|
+
}
|
|
3668
|
+
/** Reset cached embedder failure state — next call will retry the model load. */
|
|
3669
|
+
resetEmbedder() {
|
|
3670
|
+
resetEmbedder();
|
|
3671
|
+
}
|
|
3250
3672
|
/** Embedding search returning {engram, score}[] with cosine similarity scores. Async, no API calls. */
|
|
3251
3673
|
async similaritySearch(query, options) {
|
|
3252
3674
|
const filtered = this._filterEngrams(options);
|
|
@@ -3375,11 +3797,16 @@ var Plur = class {
|
|
|
3375
3797
|
let embeddingBoosts;
|
|
3376
3798
|
try {
|
|
3377
3799
|
const engrams = this._loadAllEngrams().filter((e) => e.status === "active");
|
|
3378
|
-
const results = await
|
|
3800
|
+
const results = await embeddingSearchWithScores(
|
|
3801
|
+
engrams,
|
|
3802
|
+
task,
|
|
3803
|
+
engrams.length,
|
|
3804
|
+
this.paths.root
|
|
3805
|
+
);
|
|
3379
3806
|
if (results.length > 0) {
|
|
3380
3807
|
embeddingBoosts = /* @__PURE__ */ new Map();
|
|
3381
|
-
for (
|
|
3382
|
-
embeddingBoosts.set(
|
|
3808
|
+
for (const r of results) {
|
|
3809
|
+
embeddingBoosts.set(r.engram.id, r.score);
|
|
3383
3810
|
}
|
|
3384
3811
|
}
|
|
3385
3812
|
} catch {
|
|
@@ -3510,6 +3937,28 @@ var Plur = class {
|
|
|
3510
3937
|
return true;
|
|
3511
3938
|
});
|
|
3512
3939
|
}
|
|
3940
|
+
/**
|
|
3941
|
+
* Toggle the always-load (pinned) flag for an engram.
|
|
3942
|
+
* Returns the updated engram on success, null if not found.
|
|
3943
|
+
*/
|
|
3944
|
+
setPinned(id, pinned) {
|
|
3945
|
+
return withLock(this.paths.engrams, () => {
|
|
3946
|
+
const engrams = loadEngrams(this.paths.engrams);
|
|
3947
|
+
const idx = engrams.findIndex((e2) => e2.id === id);
|
|
3948
|
+
if (idx === -1) return null;
|
|
3949
|
+
const e = engrams[idx];
|
|
3950
|
+
const updated = { ...e, pinned: pinned === true ? true : void 0 };
|
|
3951
|
+
engrams[idx] = updated;
|
|
3952
|
+
this._writeEngrams(this.paths.engrams, engrams);
|
|
3953
|
+
this._syncIndex();
|
|
3954
|
+
return updated;
|
|
3955
|
+
});
|
|
3956
|
+
}
|
|
3957
|
+
/** List engrams that have pinned: true. */
|
|
3958
|
+
listPinned() {
|
|
3959
|
+
const all = this._loadAllEngrams();
|
|
3960
|
+
return all.filter((e) => e.pinned === true && e.status === "active");
|
|
3961
|
+
}
|
|
3513
3962
|
/** Set engram status to 'retired'. Supports primary and store engrams. */
|
|
3514
3963
|
forget(id, reason) {
|
|
3515
3964
|
const foundInPrimary = withLock(this.paths.engrams, () => {
|
|
@@ -3846,17 +4295,38 @@ Generate an improved version of the procedure that prevents this failure. Return
|
|
|
3846
4295
|
versioned_engram_count: versionedCount
|
|
3847
4296
|
};
|
|
3848
4297
|
}
|
|
3849
|
-
/**
|
|
4298
|
+
/**
|
|
4299
|
+
* Register an additional engram store.
|
|
4300
|
+
*
|
|
4301
|
+
* Two shapes — exactly one of `pathOrUrl` semantics applies:
|
|
4302
|
+
* - filesystem (default): pass a path. `options.url` undefined.
|
|
4303
|
+
* - remote (PLUR Enterprise / any compatible REST API):
|
|
4304
|
+
* pass any string for the first arg (it goes into a slot we
|
|
4305
|
+
* never read), set `options.url` + `options.token`.
|
|
4306
|
+
*
|
|
4307
|
+
* Backwards compatible: existing call sites that pass a filesystem
|
|
4308
|
+
* path keep working.
|
|
4309
|
+
*/
|
|
3850
4310
|
addStore(storePath, scope, options) {
|
|
3851
4311
|
const config = loadConfig(this.paths.config);
|
|
3852
|
-
const
|
|
4312
|
+
const isRemote = Boolean(options?.url);
|
|
4313
|
+
const dedupKey = isRemote ? options.url : storePath;
|
|
4314
|
+
const existing = config.stores?.find((s) => isRemote ? s.url === dedupKey : s.path === dedupKey);
|
|
3853
4315
|
if (existing) return;
|
|
3854
|
-
const
|
|
4316
|
+
const newEntry = isRemote ? {
|
|
4317
|
+
url: options.url,
|
|
4318
|
+
token: options.token,
|
|
4319
|
+
scope,
|
|
4320
|
+
shared: options?.shared ?? true,
|
|
4321
|
+
// remote stores are shared by definition
|
|
4322
|
+
readonly: options?.readonly ?? false
|
|
4323
|
+
} : {
|
|
3855
4324
|
path: storePath,
|
|
3856
4325
|
scope,
|
|
3857
4326
|
shared: options?.shared ?? false,
|
|
3858
4327
|
readonly: options?.readonly ?? false
|
|
3859
|
-
}
|
|
4328
|
+
};
|
|
4329
|
+
const stores = [...config.stores ?? [], newEntry];
|
|
3860
4330
|
let configData = {};
|
|
3861
4331
|
try {
|
|
3862
4332
|
const raw = fs4.readFileSync(this.paths.config, "utf8");
|
|
@@ -3875,20 +4345,19 @@ Generate an improved version of the procedure that prevents this failure. Return
|
|
|
3875
4345
|
autoDiscoverStores(cwd) {
|
|
3876
4346
|
const startDir = cwd || process.cwd();
|
|
3877
4347
|
const discovered = [];
|
|
3878
|
-
const tmpDir =
|
|
4348
|
+
const tmpDir = tmpdir();
|
|
3879
4349
|
if (this.paths.root.startsWith(tmpDir) || this.paths.root.startsWith("/tmp/")) {
|
|
3880
4350
|
return discovered;
|
|
3881
4351
|
}
|
|
3882
4352
|
const knownPaths = new Set((this.config.stores ?? []).map((s) => s.path));
|
|
3883
|
-
const primaryDir =
|
|
4353
|
+
const primaryDir = dirname2(this.paths.engrams);
|
|
3884
4354
|
let dir = startDir;
|
|
3885
|
-
const { join: join5, dirname: dirname3, basename: basename2 } = path3;
|
|
3886
4355
|
const visited = /* @__PURE__ */ new Set();
|
|
3887
4356
|
while (dir && !visited.has(dir)) {
|
|
3888
4357
|
visited.add(dir);
|
|
3889
4358
|
const candidate = join5(dir, ".plur", "engrams.yaml");
|
|
3890
4359
|
if (join5(dir, ".plur") === primaryDir) {
|
|
3891
|
-
dir =
|
|
4360
|
+
dir = dirname2(dir);
|
|
3892
4361
|
continue;
|
|
3893
4362
|
}
|
|
3894
4363
|
if (fs4.existsSync(candidate) && !knownPaths.has(candidate)) {
|
|
@@ -3907,7 +4376,7 @@ Generate an improved version of the procedure that prevents this failure. Return
|
|
|
3907
4376
|
logger.info(`Auto-discovered project store: ${candidate} (${scope})`);
|
|
3908
4377
|
}
|
|
3909
4378
|
if (fs4.existsSync(join5(dir, ".git"))) break;
|
|
3910
|
-
const parent =
|
|
4379
|
+
const parent = dirname2(dir);
|
|
3911
4380
|
if (parent === dir) break;
|
|
3912
4381
|
dir = parent;
|
|
3913
4382
|
}
|
|
@@ -3925,29 +4394,57 @@ Generate an improved version of the procedure that prevents this failure. Return
|
|
|
3925
4394
|
};
|
|
3926
4395
|
const additional = stores.map((s) => {
|
|
3927
4396
|
let count = 0;
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
4397
|
+
if (s.url) {
|
|
4398
|
+
try {
|
|
4399
|
+
count = this._loadRemoteCached(s).filter((e) => e.status !== "retired").length;
|
|
4400
|
+
} catch {
|
|
4401
|
+
}
|
|
4402
|
+
} else if (s.path) {
|
|
4403
|
+
try {
|
|
4404
|
+
count = this._loadCached(s.path).filter((e) => e.status !== "retired").length;
|
|
4405
|
+
} catch {
|
|
4406
|
+
}
|
|
3931
4407
|
}
|
|
3932
|
-
return {
|
|
4408
|
+
return {
|
|
4409
|
+
path: s.path,
|
|
4410
|
+
url: s.url,
|
|
4411
|
+
scope: s.scope,
|
|
4412
|
+
shared: s.shared,
|
|
4413
|
+
readonly: s.readonly,
|
|
4414
|
+
engram_count: count
|
|
4415
|
+
};
|
|
3933
4416
|
});
|
|
3934
4417
|
return [primary, ...additional];
|
|
3935
4418
|
}
|
|
3936
4419
|
};
|
|
3937
4420
|
export {
|
|
3938
4421
|
ALL_MIGRATIONS,
|
|
4422
|
+
CAPSULE_FLAGS,
|
|
4423
|
+
CAPSULE_FLAG_RESERVED_MASK,
|
|
4424
|
+
CAPSULE_MAGIC,
|
|
4425
|
+
CAPSULE_MAGIC_HEX,
|
|
4426
|
+
CAPSULE_SIZE_LIMITS,
|
|
3939
4427
|
COMMITMENT_MULTIPLIER,
|
|
3940
4428
|
CURRENT_SCHEMA_VERSION,
|
|
4429
|
+
CapsuleHeaderSchema,
|
|
3941
4430
|
DomainCoverageSchema,
|
|
4431
|
+
ED25519_SIG_LEN,
|
|
3942
4432
|
EvidenceEntrySchema,
|
|
4433
|
+
FORMAT_VERSION_V1,
|
|
3943
4434
|
FalsificationSchema,
|
|
3944
4435
|
HierarchyPositionSchema,
|
|
3945
4436
|
IndexedStorage,
|
|
4437
|
+
ManifestSummarySchema,
|
|
3946
4438
|
MetaConfidenceSchema,
|
|
3947
4439
|
MetaFieldSchema,
|
|
3948
4440
|
PLATITUDE_PATTERNS,
|
|
4441
|
+
PREAMBLE_LEN,
|
|
4442
|
+
PayloadDescriptorSchema,
|
|
3949
4443
|
Plur,
|
|
4444
|
+
ProducerSchema,
|
|
4445
|
+
SUPPORTED_FORMAT_VERSIONS,
|
|
3950
4446
|
SessionBreadcrumbs,
|
|
4447
|
+
SignerSchema,
|
|
3951
4448
|
SqliteStore,
|
|
3952
4449
|
StructuralTemplateSchema,
|
|
3953
4450
|
YamlStore,
|
|
@@ -3986,6 +4483,7 @@ export {
|
|
|
3986
4483
|
getCachedUpdateCheck,
|
|
3987
4484
|
getProfileForInjection,
|
|
3988
4485
|
getSchemaVersion,
|
|
4486
|
+
hasFlag,
|
|
3989
4487
|
isPlatitude,
|
|
3990
4488
|
listHistoryMonths,
|
|
3991
4489
|
loadProfileCache,
|
|
@@ -3994,8 +4492,10 @@ export {
|
|
|
3994
4492
|
needsSummary,
|
|
3995
4493
|
normalizeStatement,
|
|
3996
4494
|
organizeHierarchy,
|
|
4495
|
+
parseCapsulePreamble,
|
|
3997
4496
|
parseDedupResponse,
|
|
3998
4497
|
profileNeedsRegeneration,
|
|
4498
|
+
readCapsule,
|
|
3999
4499
|
readHistory,
|
|
4000
4500
|
readHistoryForEngram,
|
|
4001
4501
|
recallAuto,
|
|
@@ -4005,9 +4505,12 @@ export {
|
|
|
4005
4505
|
saveProfileCache,
|
|
4006
4506
|
selectModel,
|
|
4007
4507
|
selectModelForOperation,
|
|
4508
|
+
serializeCapsulePreamble,
|
|
4008
4509
|
setSchemaVersion,
|
|
4009
4510
|
strengthToStatus,
|
|
4010
4511
|
tokenSimilarity,
|
|
4011
4512
|
validateMetaEngram,
|
|
4012
|
-
|
|
4513
|
+
verifyCapsuleIntegrity,
|
|
4514
|
+
withAsyncLock,
|
|
4515
|
+
writeCapsule
|
|
4013
4516
|
};
|