@kenz1117/dsh-engram 0.7.13 → 0.7.14
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.en.md +6 -2
- package/README.md +6 -2
- package/lib/client.js +2 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +493 -4
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -23,6 +23,7 @@ const CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
|
23
23
|
"modelCacheDir",
|
|
24
24
|
"hfEndpoint",
|
|
25
25
|
"ingest",
|
|
26
|
+
"reflect",
|
|
26
27
|
"provider",
|
|
27
28
|
"model",
|
|
28
29
|
"decayAfterDays",
|
|
@@ -50,6 +51,11 @@ const INGEST_MODES = /* @__PURE__ */ new Set([
|
|
|
50
51
|
"light",
|
|
51
52
|
"eager"
|
|
52
53
|
]);
|
|
54
|
+
const REFLECT_MODES = /* @__PURE__ */ new Set([
|
|
55
|
+
"off",
|
|
56
|
+
"suggest",
|
|
57
|
+
"auto"
|
|
58
|
+
]);
|
|
53
59
|
/** Schemastery 校验面(cordis.yml 读取时校验)。 */
|
|
54
60
|
const Config = z.object({
|
|
55
61
|
dbDir: z.string(),
|
|
@@ -59,6 +65,7 @@ const Config = z.object({
|
|
|
59
65
|
modelCacheDir: z.string(),
|
|
60
66
|
hfEndpoint: z.string(),
|
|
61
67
|
ingest: z.string(),
|
|
68
|
+
reflect: z.string(),
|
|
62
69
|
provider: z.string(),
|
|
63
70
|
model: z.string(),
|
|
64
71
|
decayAfterDays: z.number().step(1).min(1).max(3650),
|
|
@@ -100,6 +107,7 @@ function resolveConfig(config = {}) {
|
|
|
100
107
|
for (const key of Object.keys(config)) if (!CONFIG_KEYS.has(key)) throw new Error(`dsh-engram: unknown config key "${key}"`);
|
|
101
108
|
if (config.legacyMigration !== void 0 && config.legacyMigration !== "eager" && config.legacyMigration !== "conservative") throw new Error("dsh-engram: legacyMigration must be one of eager|conservative");
|
|
102
109
|
if (config.ingest !== void 0 && !INGEST_MODES.has(config.ingest)) throw new Error(`dsh-engram: ingest must be one of off|light|eager, got "${String(config.ingest)}"`);
|
|
110
|
+
if (config.reflect !== void 0 && !REFLECT_MODES.has(config.reflect)) throw new Error(`dsh-engram: reflect must be one of off|suggest|auto, got "${String(config.reflect)}"`);
|
|
103
111
|
if (config.profileTopN !== void 0 && (!Number.isInteger(config.profileTopN) || config.profileTopN < 1 || config.profileTopN > 64)) throw new Error("dsh-engram: profileTopN must be an integer in [1, 64]");
|
|
104
112
|
const hasProvider = config.provider !== void 0;
|
|
105
113
|
const hasModel = config.model !== void 0;
|
|
@@ -136,6 +144,7 @@ function resolveConfig(config = {}) {
|
|
|
136
144
|
modelCacheDir: config.modelCacheDir ?? join(dbDir, "models"),
|
|
137
145
|
hfEndpoint: config.hfEndpoint,
|
|
138
146
|
ingest: config.ingest ?? "off",
|
|
147
|
+
reflect: config.reflect ?? "off",
|
|
139
148
|
routeOverride: hasProvider && hasModel ? {
|
|
140
149
|
provider: config.provider,
|
|
141
150
|
model: config.model
|
|
@@ -186,6 +195,10 @@ function asEntityId(raw) {
|
|
|
186
195
|
function asFactId(raw) {
|
|
187
196
|
return raw;
|
|
188
197
|
}
|
|
198
|
+
/** 从任意字符串铸造品牌化信念 id(存储层入库前调用)。 */
|
|
199
|
+
function asObservationId(raw) {
|
|
200
|
+
return raw;
|
|
201
|
+
}
|
|
189
202
|
/** 归一化比较键:trim + 合并连续空白 + 小写(消解第一版只做精确归一匹配,不做相似合并)。 */
|
|
190
203
|
function normalizeEntityName(raw) {
|
|
191
204
|
return raw.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -1709,6 +1722,241 @@ function cosine$1(a, b) {
|
|
|
1709
1722
|
return denom === 0 ? 0 : dot / denom;
|
|
1710
1723
|
}
|
|
1711
1724
|
//#endregion
|
|
1725
|
+
//#region src/consolidation/reflect.ts
|
|
1726
|
+
/**
|
|
1727
|
+
* 信念巩固(Reflect):把尚未成为任何信念证据的原始记忆交辅助 LLM 巩固为一句话信念
|
|
1728
|
+
* (observation),并对被新证据波及的 stale 信念做新鲜度复核。
|
|
1729
|
+
*
|
|
1730
|
+
* 与 flywheel/distill 的区别:蒸馏是「合并归档」(旧记忆进 supersedes 链后消失于 active),
|
|
1731
|
+
* 巩固是「抽象叠加」(原始记忆全部保留,信念是带证据引用的额外一层;同一主题经向量近邻
|
|
1732
|
+
* 归并持续细化同一条,而非反复新增)。
|
|
1733
|
+
*
|
|
1734
|
+
* 四个动作:
|
|
1735
|
+
* - form :从新证据形成信念;向量近邻达到阈值时并入既有信念(refine),否则新建
|
|
1736
|
+
* - refine :重写 stale 信念的表述并并入新证据(status 回 active)
|
|
1737
|
+
* - still :stale 信念经证据复核仍成立(仅刷新复核时间)
|
|
1738
|
+
* - refute :stale 信念被证据否定(status 置 refuted,保留证据链供审计)
|
|
1739
|
+
* @module @kenz1117/dsh-engram/consolidation/reflect
|
|
1740
|
+
*/
|
|
1741
|
+
/** 取材池大小:从 active 记忆按 importance/confidence 倒序扫描的上限。 */
|
|
1742
|
+
const REFLECT_POOL = 300;
|
|
1743
|
+
/** 单次巩固喂给 LLM 的新证据条数上限(协议内常量:单次辅助调用的可控上下文)。 */
|
|
1744
|
+
const REFLECT_MAX_INPUT = 30;
|
|
1745
|
+
/** 单次喂给 LLM 的待复核 stale 信念上限。 */
|
|
1746
|
+
const REFLECT_MAX_STALE = 5;
|
|
1747
|
+
/** 单条 stale 信念附带的证据原文条数上限。 */
|
|
1748
|
+
const REFLECT_STALE_EVIDENCE_LIMIT = 6;
|
|
1749
|
+
/** stale 证据原文截断字符数(控制总上下文)。 */
|
|
1750
|
+
const REFLECT_EVIDENCE_SLICE = 120;
|
|
1751
|
+
/** 巩固输出 token 上限。 */
|
|
1752
|
+
const REFLECT_MAX_TOKENS = 900;
|
|
1753
|
+
/** 信念向量近邻归并阈值:新信念与既有信念余弦达到该值则并入同一条(持续细化)。 */
|
|
1754
|
+
const REFLECT_MERGE_THRESHOLD = .9;
|
|
1755
|
+
/** 新鲜度阈值:新证据与既有信念余弦达到该值(低于归并阈值,语义更宽)则把信念标 stale 待复核。 */
|
|
1756
|
+
const REFLECT_STALE_THRESHOLD = .86;
|
|
1757
|
+
const REFLECT_SYSTEM = [
|
|
1758
|
+
"下面给出两部分材料:【待巩固记忆】是新近写入的原始记忆;【待复核信念】是此前巩固出、但可能受新证据影响的信念(附原始证据)。",
|
|
1759
|
+
"请对材料做巩固与复核,只输出一个 JSON 数组,每项为以下四种动作之一:",
|
|
1760
|
+
"1. {\"action\":\"form\",\"belief\":\"巩固出的一句话信念(结论/规律/稳定偏好)\",\"sourceIds\":[\"证据记忆id\"]} —— sourceIds 至少 1 个且必须全部来自【待巩固记忆】的 id;多条记忆支撑同一信念时合并成一条,不要为单条琐碎记忆立信念。",
|
|
1761
|
+
"2. {\"action\":\"refine\",\"observationId\":\"待复核信念id\",\"belief\":\"结合新证据后的新表述\",\"sourceIds\":[\"新增证据记忆id\"]} —— 新证据补充或限定了该信念时使用;sourceIds 必须来自【待巩固记忆】,可为空数组(仅表述更准确)。",
|
|
1762
|
+
"3. {\"action\":\"still\",\"observationId\":\"待复核信念id\"} —— 新证据不影响该信念,原表述仍成立。",
|
|
1763
|
+
"4. {\"action\":\"refute\",\"observationId\":\"待复核信念id\",\"reason\":\"被否定的原因(引用证据)\"} —— 新证据与信念矛盾且原信念不再成立。",
|
|
1764
|
+
"observationId 必须取自【待复核信念】给出的 id。材料中没有值得巩固或复核的内容时输出 []。不要输出 JSON 以外的任何内容。"
|
|
1765
|
+
].join("\n");
|
|
1766
|
+
/**
|
|
1767
|
+
* 执行一次信念巩固。纯异步:LLM/解析失败抛错(调用层决定是否落 pending),
|
|
1768
|
+
* 单条动作的写入失败计入 skipped 不中断整批。
|
|
1769
|
+
*/
|
|
1770
|
+
async function reflectObservations(deps) {
|
|
1771
|
+
const outcome = {
|
|
1772
|
+
pool: 0,
|
|
1773
|
+
candidates: 0,
|
|
1774
|
+
staleReviewed: 0,
|
|
1775
|
+
formed: 0,
|
|
1776
|
+
refined: 0,
|
|
1777
|
+
confirmed: 0,
|
|
1778
|
+
refuted: 0,
|
|
1779
|
+
skipped: 0
|
|
1780
|
+
};
|
|
1781
|
+
const pool = await deps.store.topActive(deps.scope, REFLECT_POOL);
|
|
1782
|
+
outcome.pool = pool.length;
|
|
1783
|
+
const known = await deps.store.listObservations({ limit: 1e3 });
|
|
1784
|
+
const evidenced = /* @__PURE__ */ new Set();
|
|
1785
|
+
for (const observation of known) {
|
|
1786
|
+
if (observation.status === "refuted") continue;
|
|
1787
|
+
for (const sourceId of observation.sourceIds) evidenced.add(String(sourceId));
|
|
1788
|
+
}
|
|
1789
|
+
const candidates = pool.filter((record) => !evidenced.has(String(record.id))).slice(0, REFLECT_MAX_INPUT);
|
|
1790
|
+
if (deps.embedder !== void 0 && candidates.length > 0) {
|
|
1791
|
+
const vectors = await deps.embedder.embed(candidates.map((record) => record.content));
|
|
1792
|
+
const staleMarks = /* @__PURE__ */ new Set();
|
|
1793
|
+
for (const vector of vectors) {
|
|
1794
|
+
if (vector === void 0) continue;
|
|
1795
|
+
const nearest = await deps.store.nearestObservation(vector);
|
|
1796
|
+
if (nearest !== void 0 && nearest.record.status === "active" && nearest.similarity >= REFLECT_STALE_THRESHOLD) staleMarks.add(String(nearest.record.id));
|
|
1797
|
+
}
|
|
1798
|
+
for (const id of staleMarks) try {
|
|
1799
|
+
await deps.store.markObservationStale(asObservationId(id));
|
|
1800
|
+
} catch {}
|
|
1801
|
+
}
|
|
1802
|
+
const stale = await deps.store.listObservations({
|
|
1803
|
+
status: "stale",
|
|
1804
|
+
limit: REFLECT_MAX_STALE
|
|
1805
|
+
});
|
|
1806
|
+
outcome.candidates = candidates.length;
|
|
1807
|
+
outcome.staleReviewed = stale.length;
|
|
1808
|
+
if (candidates.length === 0 && stale.length === 0) return outcome;
|
|
1809
|
+
const candidateIds = new Set(candidates.map((record) => String(record.id)));
|
|
1810
|
+
const staleIds = new Set(stale.map((observation) => String(observation.id)));
|
|
1811
|
+
const sections = [];
|
|
1812
|
+
if (candidates.length > 0) {
|
|
1813
|
+
const lines = candidates.map((record) => `[${String(record.id)}] (${record.kind}, importance ${record.importance.toFixed(2)}) ${record.content}`);
|
|
1814
|
+
sections.push(`【待巩固记忆】\n${lines.join("\n")}`);
|
|
1815
|
+
}
|
|
1816
|
+
if (stale.length > 0) {
|
|
1817
|
+
const blocks = [];
|
|
1818
|
+
for (const observation of stale) {
|
|
1819
|
+
const evidence = (await deps.store.getMany(observation.sourceIds.slice(0, REFLECT_STALE_EVIDENCE_LIMIT))).map((record) => ` - ${record.content.slice(0, REFLECT_EVIDENCE_SLICE)}`).join("\n");
|
|
1820
|
+
blocks.push(`[${String(observation.id)}] 证据 ${String(observation.proofCount)} 条:${observation.belief}\n${evidence}`);
|
|
1821
|
+
}
|
|
1822
|
+
sections.push(`【待复核信念】\n${blocks.join("\n")}`);
|
|
1823
|
+
}
|
|
1824
|
+
const userText = sections.join("\n\n");
|
|
1825
|
+
deps.logRequest({
|
|
1826
|
+
route: deps.route,
|
|
1827
|
+
scope: deps.scope,
|
|
1828
|
+
candidateCount: candidates.length,
|
|
1829
|
+
staleCount: stale.length,
|
|
1830
|
+
maxTokens: REFLECT_MAX_TOKENS
|
|
1831
|
+
});
|
|
1832
|
+
const parsed = parseJsonArray(await deps.call({
|
|
1833
|
+
route: deps.route,
|
|
1834
|
+
system: REFLECT_SYSTEM,
|
|
1835
|
+
userText,
|
|
1836
|
+
maxTokens: REFLECT_MAX_TOKENS,
|
|
1837
|
+
purpose: "engram-reflect",
|
|
1838
|
+
signal: deps.signal
|
|
1839
|
+
}));
|
|
1840
|
+
if (parsed === void 0 || parsed.length === 0) return outcome;
|
|
1841
|
+
for (const item of parsed) {
|
|
1842
|
+
const action = item;
|
|
1843
|
+
try {
|
|
1844
|
+
if (action.action === "form") {
|
|
1845
|
+
if (await applyForm(deps, action, candidateIds) === "refined") outcome.refined += 1;
|
|
1846
|
+
else outcome.formed += 1;
|
|
1847
|
+
} else if (action.action === "refine") {
|
|
1848
|
+
const observationId = requireObservationId(action.observationId, staleIds);
|
|
1849
|
+
if (observationId === void 0) {
|
|
1850
|
+
outcome.skipped += 1;
|
|
1851
|
+
continue;
|
|
1852
|
+
}
|
|
1853
|
+
if (typeof action.belief !== "string" || action.belief.trim() === "") {
|
|
1854
|
+
outcome.skipped += 1;
|
|
1855
|
+
continue;
|
|
1856
|
+
}
|
|
1857
|
+
const sourceIds = requireCandidateIds(action.sourceIds, candidateIds);
|
|
1858
|
+
if (sourceIds === void 0) {
|
|
1859
|
+
outcome.skipped += 1;
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
await refineWithEmbedding(deps, observationId, action.belief.trim(), sourceIds);
|
|
1863
|
+
outcome.refined += 1;
|
|
1864
|
+
} else if (action.action === "still") {
|
|
1865
|
+
const observationId = requireObservationId(action.observationId, staleIds);
|
|
1866
|
+
if (observationId === void 0) {
|
|
1867
|
+
outcome.skipped += 1;
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
await deps.store.confirmObservation(observationId);
|
|
1871
|
+
outcome.confirmed += 1;
|
|
1872
|
+
} else if (action.action === "refute") {
|
|
1873
|
+
const observationId = requireObservationId(action.observationId, staleIds);
|
|
1874
|
+
if (observationId === void 0) {
|
|
1875
|
+
outcome.skipped += 1;
|
|
1876
|
+
continue;
|
|
1877
|
+
}
|
|
1878
|
+
await deps.store.refuteObservation(observationId);
|
|
1879
|
+
outcome.refuted += 1;
|
|
1880
|
+
} else outcome.skipped += 1;
|
|
1881
|
+
} catch {
|
|
1882
|
+
outcome.skipped += 1;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
try {
|
|
1886
|
+
await deps.store.audit("reflect", "AUX", JSON.stringify({
|
|
1887
|
+
scope: deps.scope,
|
|
1888
|
+
pool: outcome.pool,
|
|
1889
|
+
candidates: outcome.candidates,
|
|
1890
|
+
staleReviewed: outcome.staleReviewed,
|
|
1891
|
+
formed: outcome.formed,
|
|
1892
|
+
refined: outcome.refined,
|
|
1893
|
+
confirmed: outcome.confirmed,
|
|
1894
|
+
refuted: outcome.refuted,
|
|
1895
|
+
skipped: outcome.skipped
|
|
1896
|
+
}));
|
|
1897
|
+
} catch {}
|
|
1898
|
+
return outcome;
|
|
1899
|
+
}
|
|
1900
|
+
/** 处理 form:校验信念与证据,算向量并决定近邻归并或新建。@returns 落库形态。 */
|
|
1901
|
+
async function applyForm(deps, action, candidateIds) {
|
|
1902
|
+
if (typeof action.belief !== "string" || action.belief.trim() === "") throw new Error("form: belief 缺失");
|
|
1903
|
+
const sourceIds = requireCandidateIds(action.sourceIds, candidateIds);
|
|
1904
|
+
if (sourceIds === void 0 || sourceIds.length === 0) throw new Error("form: sourceIds 越界或为空");
|
|
1905
|
+
const belief = action.belief.trim();
|
|
1906
|
+
if (deps.embedder !== void 0) {
|
|
1907
|
+
const vector = (await deps.embedder.embed([belief]))[0];
|
|
1908
|
+
if (vector !== void 0) {
|
|
1909
|
+
const nearest = await deps.store.nearestObservation(vector);
|
|
1910
|
+
if (nearest !== void 0 && nearest.similarity >= REFLECT_MERGE_THRESHOLD) {
|
|
1911
|
+
await deps.store.refineObservation(nearest.record.id, belief, sourceIds, vector);
|
|
1912
|
+
return "refined";
|
|
1913
|
+
}
|
|
1914
|
+
await deps.store.createObservation({
|
|
1915
|
+
scope: deps.scope,
|
|
1916
|
+
belief,
|
|
1917
|
+
sourceIds,
|
|
1918
|
+
embedding: vector
|
|
1919
|
+
});
|
|
1920
|
+
return "formed";
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
await deps.store.createObservation({
|
|
1924
|
+
scope: deps.scope,
|
|
1925
|
+
belief,
|
|
1926
|
+
sourceIds
|
|
1927
|
+
});
|
|
1928
|
+
return "formed";
|
|
1929
|
+
}
|
|
1930
|
+
/** refine 时同样尝试为新表述算向量(失败则只改文本与证据,保留旧向量)。 */
|
|
1931
|
+
async function refineWithEmbedding(deps, observationId, belief, sourceIds) {
|
|
1932
|
+
if (deps.embedder !== void 0) {
|
|
1933
|
+
const vector = (await deps.embedder.embed([belief]))[0];
|
|
1934
|
+
if (vector !== void 0) {
|
|
1935
|
+
await deps.store.refineObservation(observationId, belief, sourceIds, vector);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
await deps.store.refineObservation(observationId, belief, sourceIds);
|
|
1940
|
+
}
|
|
1941
|
+
/** 校验 observationId 是本次喂入的 stale 信念;非法返回 undefined(跳过该动作)。 */
|
|
1942
|
+
function requireObservationId(raw, staleIds) {
|
|
1943
|
+
if (typeof raw !== "string" || !staleIds.has(raw)) return void 0;
|
|
1944
|
+
return asObservationId(raw);
|
|
1945
|
+
}
|
|
1946
|
+
/**
|
|
1947
|
+
* 校验 sourceIds:必须为数组、每个 id 都来自本次候选集;返回品牌化 id 列表。
|
|
1948
|
+
* 非数组或含越界 id 返回 undefined(动作跳过);空数组合法(refine 仅改表述时使用)。
|
|
1949
|
+
*/
|
|
1950
|
+
function requireCandidateIds(raw, candidateIds) {
|
|
1951
|
+
if (!Array.isArray(raw)) return void 0;
|
|
1952
|
+
const ids = [];
|
|
1953
|
+
for (const item of raw) {
|
|
1954
|
+
if (typeof item !== "string" || !candidateIds.has(item)) return void 0;
|
|
1955
|
+
ids.push(asMemoryId(item));
|
|
1956
|
+
}
|
|
1957
|
+
return ids;
|
|
1958
|
+
}
|
|
1959
|
+
//#endregion
|
|
1712
1960
|
//#region src/jev/client.ts
|
|
1713
1961
|
/**
|
|
1714
1962
|
* 调用 Jev /v1/systemone 端点并行回答 Noul 问题。
|
|
@@ -3642,7 +3890,7 @@ function placardImprovementHint(score) {
|
|
|
3642
3890
|
* @module @kenz1117/dsh-engram/store/sqlite
|
|
3643
3891
|
*/
|
|
3644
3892
|
/** 当前 schema 版本;结构性变更必须 +1。可空列与伴随表走增量迁移(见 openEngramStore 的迁移段)。 */
|
|
3645
|
-
const SCHEMA_VERSION =
|
|
3893
|
+
const SCHEMA_VERSION = 12;
|
|
3646
3894
|
/** 增量迁移表:key 为起始版本,value 为升到下一版本的 SQL(可多语句)。
|
|
3647
3895
|
* v2 → v3:nodes 补可空列 outcome(使用效果回报)。
|
|
3648
3896
|
* v3 → v4:新增 nodes_revisions 修订表(update 归档旧条目时的内容快照)。
|
|
@@ -3662,7 +3910,11 @@ const SCHEMA_VERSION = 11;
|
|
|
3662
3910
|
* v10 → v11:facts 事实表——摄取/工具期抽取的一句话事实,挂在实体上(entity_id
|
|
3663
3911
|
* 引用同库 entities.id,无外键)。valid_at/invalid_at 时间窗 + replaced_by 软失效
|
|
3664
3912
|
* 链:新事实可声明取代旧事实(旧事实置 invalid_at 并回指 replaced_by),历史链
|
|
3665
|
-
* 保留可审计;asOf 查询按时间窗过滤(见 factsOfEntity)。
|
|
3913
|
+
* 保留可审计;asOf 查询按时间窗过滤(见 factsOfEntity)。
|
|
3914
|
+
* v11 → v12:observations 巩固信念表——辅助 LLM 从多条原始记忆巩固出的一句话信念,
|
|
3915
|
+
* sources_json 证据 id 列表 + proof_count 证据数(去重并集持续累积),status 三态
|
|
3916
|
+
* active/stale/refuted 支持新鲜度复核;embedding 支持近邻归并(同主题并入同一条而非
|
|
3917
|
+
* 重复新增)。与 nodes 无外键(源记忆可被归档/遗忘,证据链只保留 id 引用)。 */
|
|
3666
3918
|
const MIGRATIONS = {
|
|
3667
3919
|
"2": "ALTER TABLE nodes ADD COLUMN outcome TEXT",
|
|
3668
3920
|
"3": `CREATE TABLE IF NOT EXISTS nodes_revisions (
|
|
@@ -3703,7 +3955,12 @@ const MIGRATIONS = {
|
|
|
3703
3955
|
id TEXT PRIMARY KEY, entity_id TEXT NOT NULL, content TEXT NOT NULL,
|
|
3704
3956
|
valid_at INTEGER NOT NULL, invalid_at INTEGER, replaced_by TEXT,
|
|
3705
3957
|
source_node_id TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL);
|
|
3706
|
-
CREATE INDEX IF NOT EXISTS facts_entity ON facts (entity_id)
|
|
3958
|
+
CREATE INDEX IF NOT EXISTS facts_entity ON facts (entity_id);`,
|
|
3959
|
+
"11": `CREATE TABLE IF NOT EXISTS observations (
|
|
3960
|
+
id TEXT PRIMARY KEY, scope TEXT NOT NULL, belief TEXT NOT NULL, status TEXT NOT NULL,
|
|
3961
|
+
sources_json TEXT NOT NULL, proof_count INTEGER NOT NULL, embedding BLOB,
|
|
3962
|
+
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, last_validated_at INTEGER NOT NULL);
|
|
3963
|
+
CREATE INDEX IF NOT EXISTS observations_status ON observations (status, proof_count, updated_at);`
|
|
3707
3964
|
};
|
|
3708
3965
|
/** RRF 融合常数:score = Σ 1/(K + rank)。 */
|
|
3709
3966
|
const RRF_K = 60;
|
|
@@ -3812,6 +4069,27 @@ function factRowToRecord(row) {
|
|
|
3812
4069
|
function vecToBlob(vector) {
|
|
3813
4070
|
return new Uint8Array(vector.buffer, vector.byteOffset, vector.byteLength);
|
|
3814
4071
|
}
|
|
4072
|
+
/** 行 → ObservationRecord;sources_json 由本插件自写,解析失败按空证据兜底(不炸读侧)。 */
|
|
4073
|
+
function observationRowToRecord(row) {
|
|
4074
|
+
let sourceIds = [];
|
|
4075
|
+
try {
|
|
4076
|
+
const parsed = JSON.parse(row.sources_json);
|
|
4077
|
+
if (Array.isArray(parsed)) sourceIds = parsed.filter((item) => typeof item === "string").map(asMemoryId);
|
|
4078
|
+
} catch {
|
|
4079
|
+
sourceIds = [];
|
|
4080
|
+
}
|
|
4081
|
+
return {
|
|
4082
|
+
id: asObservationId(row.id),
|
|
4083
|
+
scope: row.scope,
|
|
4084
|
+
belief: row.belief,
|
|
4085
|
+
status: row.status,
|
|
4086
|
+
sourceIds,
|
|
4087
|
+
proofCount: row.proof_count,
|
|
4088
|
+
createdAt: row.created_at,
|
|
4089
|
+
updatedAt: row.updated_at,
|
|
4090
|
+
lastValidatedAt: row.last_validated_at
|
|
4091
|
+
};
|
|
4092
|
+
}
|
|
3815
4093
|
/** 余弦相似度;任一向量零范数时返回 0。 */
|
|
3816
4094
|
function cosine(a, b) {
|
|
3817
4095
|
let dot = 0;
|
|
@@ -3928,6 +4206,10 @@ async function openEngramStore(path, rankBoost = NO_BOOST, automation = DEFAULT_
|
|
|
3928
4206
|
id TEXT PRIMARY KEY, entity_id TEXT NOT NULL, content TEXT NOT NULL,
|
|
3929
4207
|
valid_at INTEGER NOT NULL, invalid_at INTEGER, replaced_by TEXT,
|
|
3930
4208
|
source_node_id TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL);
|
|
4209
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
4210
|
+
id TEXT PRIMARY KEY, scope TEXT NOT NULL, belief TEXT NOT NULL, status TEXT NOT NULL,
|
|
4211
|
+
sources_json TEXT NOT NULL, proof_count INTEGER NOT NULL, embedding BLOB,
|
|
4212
|
+
created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, last_validated_at INTEGER NOT NULL);
|
|
3931
4213
|
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(node_id UNINDEXED, content, tokenize='unicode61');
|
|
3932
4214
|
CREATE INDEX IF NOT EXISTS nodes_scope_status ON nodes (scope, status);
|
|
3933
4215
|
CREATE INDEX IF NOT EXISTS nodes_kind_created ON nodes (kind, created_at);
|
|
@@ -3935,6 +4217,7 @@ async function openEngramStore(path, rankBoost = NO_BOOST, automation = DEFAULT_
|
|
|
3935
4217
|
CREATE INDEX IF NOT EXISTS entities_name ON entities (name);
|
|
3936
4218
|
CREATE INDEX IF NOT EXISTS node_entities_entity ON node_entities (entity_id);
|
|
3937
4219
|
CREATE INDEX IF NOT EXISTS facts_entity ON facts (entity_id);
|
|
4220
|
+
CREATE INDEX IF NOT EXISTS observations_status ON observations (status, proof_count, updated_at);
|
|
3938
4221
|
`);
|
|
3939
4222
|
const versionRow = db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
|
3940
4223
|
if (versionRow === void 0) {
|
|
@@ -4070,6 +4353,18 @@ async function openEngramStore(path, rankBoost = NO_BOOST, automation = DEFAULT_
|
|
|
4070
4353
|
const sqlFactInvalidate = db.prepare("UPDATE facts SET invalid_at = ?, replaced_by = ?, updated_at = ? WHERE id = ?");
|
|
4071
4354
|
const sqlFactGet = db.prepare("SELECT * FROM facts WHERE id = ?");
|
|
4072
4355
|
const sqlPurgeFacts = db.prepare("DELETE FROM facts");
|
|
4356
|
+
const sqlObservationInsert = db.prepare(`INSERT INTO observations
|
|
4357
|
+
(id, scope, belief, status, sources_json, proof_count, embedding, created_at, updated_at, last_validated_at)
|
|
4358
|
+
VALUES (?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)`);
|
|
4359
|
+
const sqlObservationGet = db.prepare("SELECT * FROM observations WHERE id = ?");
|
|
4360
|
+
const sqlObservationUpdate = db.prepare(`UPDATE observations
|
|
4361
|
+
SET belief = ?, status = ?, sources_json = ?, proof_count = ?,
|
|
4362
|
+
embedding = COALESCE(?, embedding), updated_at = ?, last_validated_at = ? WHERE id = ?`);
|
|
4363
|
+
const sqlObservationSetStatus = db.prepare("UPDATE observations SET status = ?, updated_at = ?, last_validated_at = ? WHERE id = ?");
|
|
4364
|
+
const sqlObservationMarkStale = db.prepare("UPDATE observations SET status = 'stale' WHERE id = ?");
|
|
4365
|
+
const sqlObservationRefute = db.prepare("UPDATE observations SET status = 'refuted', updated_at = ? WHERE id = ?");
|
|
4366
|
+
const sqlObservationActiveVectors = db.prepare("SELECT * FROM observations WHERE status != 'refuted' AND embedding IS NOT NULL");
|
|
4367
|
+
const sqlPurgeObservations = db.prepare("DELETE FROM observations");
|
|
4073
4368
|
/** FTS 道:按 scope 集合检索(占位符动态生成,scope 集合由调用方去重);rooms 非空时只查指定房间。 */
|
|
4074
4369
|
const ftsSearch = (match, scopes, rooms) => {
|
|
4075
4370
|
const placeholders = scopes.map(() => "?").join(",");
|
|
@@ -4874,6 +5169,79 @@ async function openEngramStore(path, rankBoost = NO_BOOST, automation = DEFAULT_
|
|
|
4874
5169
|
total
|
|
4875
5170
|
};
|
|
4876
5171
|
},
|
|
5172
|
+
async createObservation(input) {
|
|
5173
|
+
const id = asObservationId(randomUUID());
|
|
5174
|
+
const now = Date.now();
|
|
5175
|
+
const sourceIds = [...new Set(input.sourceIds.map(String))].map(asMemoryId);
|
|
5176
|
+
if (sourceIds.length === 0) throw new EngramError("BAD_INPUT", "createObservation 要求至少 1 条证据记忆");
|
|
5177
|
+
const stored = input.embedding === void 0 ? null : vecToBlob(input.embedding);
|
|
5178
|
+
withTransaction(() => {
|
|
5179
|
+
sqlObservationInsert.run(id, input.scope, input.belief, JSON.stringify(sourceIds.map(String)), sourceIds.length, stored, now, now, now);
|
|
5180
|
+
});
|
|
5181
|
+
return {
|
|
5182
|
+
id,
|
|
5183
|
+
scope: input.scope,
|
|
5184
|
+
belief: input.belief,
|
|
5185
|
+
status: "active",
|
|
5186
|
+
sourceIds,
|
|
5187
|
+
proofCount: sourceIds.length,
|
|
5188
|
+
createdAt: now,
|
|
5189
|
+
updatedAt: now,
|
|
5190
|
+
lastValidatedAt: now
|
|
5191
|
+
};
|
|
5192
|
+
},
|
|
5193
|
+
async refineObservation(id, belief, addSourceIds, embedding) {
|
|
5194
|
+
const existing = sqlObservationGet.get(String(id));
|
|
5195
|
+
if (existing === void 0) return void 0;
|
|
5196
|
+
const merged = [.../* @__PURE__ */ new Set([...observationRowToRecord(existing).sourceIds.map(String), ...addSourceIds.map(String)])].map(asMemoryId);
|
|
5197
|
+
const now = Date.now();
|
|
5198
|
+
const stored = embedding === void 0 ? null : vecToBlob(embedding);
|
|
5199
|
+
withTransaction(() => {
|
|
5200
|
+
sqlObservationUpdate.run(belief, "active", JSON.stringify(merged.map(String)), merged.length, stored, now, now, String(id));
|
|
5201
|
+
});
|
|
5202
|
+
return observationRowToRecord(sqlObservationGet.get(String(id)));
|
|
5203
|
+
},
|
|
5204
|
+
async markObservationStale(id) {
|
|
5205
|
+
if (sqlObservationGet.get(String(id)) === void 0) return void 0;
|
|
5206
|
+
sqlObservationMarkStale.run(String(id));
|
|
5207
|
+
return observationRowToRecord(sqlObservationGet.get(String(id)));
|
|
5208
|
+
},
|
|
5209
|
+
async confirmObservation(id) {
|
|
5210
|
+
if (sqlObservationGet.get(String(id)) === void 0) return void 0;
|
|
5211
|
+
const now = Date.now();
|
|
5212
|
+
sqlObservationSetStatus.run("active", now, now, String(id));
|
|
5213
|
+
return observationRowToRecord(sqlObservationGet.get(String(id)));
|
|
5214
|
+
},
|
|
5215
|
+
async refuteObservation(id) {
|
|
5216
|
+
if (sqlObservationGet.get(String(id)) === void 0) return void 0;
|
|
5217
|
+
const now = Date.now();
|
|
5218
|
+
withTransaction(() => {
|
|
5219
|
+
sqlObservationRefute.run(now, String(id));
|
|
5220
|
+
});
|
|
5221
|
+
return observationRowToRecord(sqlObservationGet.get(String(id)));
|
|
5222
|
+
},
|
|
5223
|
+
async listObservations(filter) {
|
|
5224
|
+
const offset = Math.max(0, filter.offset ?? 0);
|
|
5225
|
+
if (filter.status === void 0) return db.prepare("SELECT * FROM observations ORDER BY proof_count DESC, updated_at DESC LIMIT ? OFFSET ?").all(filter.limit, offset).map(observationRowToRecord);
|
|
5226
|
+
return db.prepare("SELECT * FROM observations WHERE status = ? ORDER BY proof_count DESC, updated_at DESC LIMIT ? OFFSET ?").all(filter.status, filter.limit, offset).map(observationRowToRecord);
|
|
5227
|
+
},
|
|
5228
|
+
async nearestObservation(embedding) {
|
|
5229
|
+
const rows = sqlObservationActiveVectors.all();
|
|
5230
|
+
let best;
|
|
5231
|
+
let bestSim = 0;
|
|
5232
|
+
for (const row of rows) {
|
|
5233
|
+
if (row.embedding === null) continue;
|
|
5234
|
+
const sim = cosine(embedding, blobToVec(row.embedding));
|
|
5235
|
+
if (sim > bestSim) {
|
|
5236
|
+
bestSim = sim;
|
|
5237
|
+
best = row;
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
return best === void 0 ? void 0 : {
|
|
5241
|
+
record: observationRowToRecord(best),
|
|
5242
|
+
similarity: bestSim
|
|
5243
|
+
};
|
|
5244
|
+
},
|
|
4877
5245
|
async audit(op, targetId, detail) {
|
|
4878
5246
|
sqlLog.run(Date.now(), op, targetId, detail);
|
|
4879
5247
|
},
|
|
@@ -4899,6 +5267,7 @@ async function openEngramStore(path, rankBoost = NO_BOOST, automation = DEFAULT_
|
|
|
4899
5267
|
sqlPurgeNodeEntities.run();
|
|
4900
5268
|
sqlPurgeEntities.run();
|
|
4901
5269
|
sqlPurgeFacts.run();
|
|
5270
|
+
sqlPurgeObservations.run();
|
|
4902
5271
|
});
|
|
4903
5272
|
},
|
|
4904
5273
|
async close() {
|
|
@@ -5277,7 +5646,7 @@ function assessEvidence(batch, request) {
|
|
|
5277
5646
|
//#endregion
|
|
5278
5647
|
//#region src/tools/create.ts
|
|
5279
5648
|
/**
|
|
5280
|
-
*
|
|
5649
|
+
* 21 个 engram_ 工具的定义与执行器。工具 schema 保持窄参数;
|
|
5281
5650
|
* scope 决定读写哪个分库;嵌入缺失时检索结果显式标记降级。
|
|
5282
5651
|
* @module @kenz1117/dsh-engram/tools/create
|
|
5283
5652
|
*/
|
|
@@ -7207,6 +7576,67 @@ function createEngramTools(baseDeps) {
|
|
|
7207
7576
|
return { text: `蒸馏完成:取材 ${outcome.input} 条,产出 ${outcome.distilled} 条高层规律,归档 ${outcome.superseded} 条旧记忆(supersedes 链已建立,可 engram_review 审计)。` };
|
|
7208
7577
|
}
|
|
7209
7578
|
}),
|
|
7579
|
+
defineTool({
|
|
7580
|
+
name: "engram_reflect",
|
|
7581
|
+
description: "信念巩固:把多条原始记忆经辅助 LLM 巩固为带证据数的高层信念(form 新建/refine 细化/still 维持/refute 否定),同主题自动归并;原始记忆保留不归档。反复出现的模式或新增一批记忆后调用。",
|
|
7582
|
+
parameters: { scope: {
|
|
7583
|
+
type: "string",
|
|
7584
|
+
enum: [
|
|
7585
|
+
"user",
|
|
7586
|
+
"project",
|
|
7587
|
+
"shared"
|
|
7588
|
+
],
|
|
7589
|
+
description: "作用域,默认 user"
|
|
7590
|
+
} },
|
|
7591
|
+
output: {
|
|
7592
|
+
schema: {
|
|
7593
|
+
type: "object",
|
|
7594
|
+
additionalProperties: false,
|
|
7595
|
+
properties: { text: {
|
|
7596
|
+
type: "string",
|
|
7597
|
+
required: true
|
|
7598
|
+
} }
|
|
7599
|
+
},
|
|
7600
|
+
render: (_args, value) => [{
|
|
7601
|
+
type: "text",
|
|
7602
|
+
text: value.text
|
|
7603
|
+
}]
|
|
7604
|
+
},
|
|
7605
|
+
async execute(args, exec) {
|
|
7606
|
+
const scope = scopeOf(args.scope, "user");
|
|
7607
|
+
if (deps.call === void 0) throw new Error("engram_reflect: 辅助 LLM 不可用(宿主未提供 llm 服务),无法巩固");
|
|
7608
|
+
const call = deps.call;
|
|
7609
|
+
const events = exec.agent?.session?.snapshotEvents() ?? [];
|
|
7610
|
+
const route = deps.routeOverride ?? routeFromEvents(events);
|
|
7611
|
+
if (route === void 0) throw new Error("engram_reflect: 无法确定模型路由(会话尚无模型请求),请在 cordis.yml 配置 provider/model");
|
|
7612
|
+
const store = await deps.openStore(scope);
|
|
7613
|
+
const outcome = await reflectObservations({
|
|
7614
|
+
store,
|
|
7615
|
+
embedder: await deps.embedder,
|
|
7616
|
+
scope,
|
|
7617
|
+
call: (params) => call({
|
|
7618
|
+
...params,
|
|
7619
|
+
sessionId: exec.agent === void 0 ? void 0 : String(exec.agent.session.id)
|
|
7620
|
+
}),
|
|
7621
|
+
logRequest: (data) => {
|
|
7622
|
+
store.audit("reflect-request", "AUX", JSON.stringify(data)).catch(() => {});
|
|
7623
|
+
},
|
|
7624
|
+
route,
|
|
7625
|
+
signal: exec.signal
|
|
7626
|
+
});
|
|
7627
|
+
const pending = await store.listAuditDetails("reflect-pending");
|
|
7628
|
+
let consumed = 0;
|
|
7629
|
+
for (const detail of pending) try {
|
|
7630
|
+
if (JSON.parse(detail).scope === scope) {
|
|
7631
|
+
await store.clearAudit("reflect-pending", detail);
|
|
7632
|
+
consumed += 1;
|
|
7633
|
+
}
|
|
7634
|
+
} catch {}
|
|
7635
|
+
const lines = [`信念巩固完成:取材池 ${String(outcome.pool)} 条,新证据 ${String(outcome.candidates)} 条、待复核信念 ${String(outcome.staleReviewed)} 条;新建 ${String(outcome.formed)},细化 ${String(outcome.refined)},维持 ${String(outcome.confirmed)},否定 ${String(outcome.refuted)},跳过 ${String(outcome.skipped)}。`, "信念是叠加在原始记忆之上的抽象层(证据 id 与证据数已留存,原始记忆未归档)。"];
|
|
7636
|
+
if (consumed > 0) lines.push(`同时消费了 ${String(consumed)} 条会话结束时登记的待巩固任务。`);
|
|
7637
|
+
return { text: lines.join("\n") };
|
|
7638
|
+
}
|
|
7639
|
+
}),
|
|
7210
7640
|
defineTool({
|
|
7211
7641
|
name: "engram_examine",
|
|
7212
7642
|
description: "渐进式披露:按 id 批量拉取记忆完整铭牌(content + 房间 + 状态 + 边关系)。仅在已通过 engram_search/timeline/neighbors 拿到候选 id 后调用,避免一次性吞全文。建议 ≤16 个 id,超出会按入参顺序保留前 N 条。",
|
|
@@ -7764,6 +8194,12 @@ function renderProfileWithCurated(curated, records, tokenBudget, itemBudget = DE
|
|
|
7764
8194
|
const COMPRESS_MAX_TOKENS = 800;
|
|
7765
8195
|
/** 压缩辅助调用超时:压缩在 pre-step 关键路径上,必须限时防阻塞首轮请求。 */
|
|
7766
8196
|
const COMPRESS_TIMEOUT_MS = 8e3;
|
|
8197
|
+
/** turn_start 注入的巩固信念条数上限(按证据数、新近度倒序)。 */
|
|
8198
|
+
const OBSERVATION_INJECT_LIMIT = 3;
|
|
8199
|
+
/** 单条信念注入截断字符数。 */
|
|
8200
|
+
const OBSERVATION_INJECT_ITEM_CHARS = 120;
|
|
8201
|
+
/** 信念区块总字符上限(独立于画像 token 预算,避免与画像互相挤压)。 */
|
|
8202
|
+
const OBSERVATION_INJECT_BLOCK_CHARS = 480;
|
|
7767
8203
|
const COMPRESS_SYSTEM = [
|
|
7768
8204
|
"把记忆条目压缩为更短的一句话表述(每条不超过 40 个字符),保留可跨会话复用的关键信息(事实、偏好、决策、方法)。",
|
|
7769
8205
|
"只输出一个 JSON 数组,每项形如 {\"id\": \"原样返回的id\", \"content\": \"压缩后表述\"},条目数量与 id 必须与输入一一对应。",
|
|
@@ -7918,6 +8354,14 @@ async function preStep(ctx, openStore, openStoreForProjectCwd, resolved, embedde
|
|
|
7918
8354
|
}).text;
|
|
7919
8355
|
}
|
|
7920
8356
|
}
|
|
8357
|
+
const observations = await store.listObservations({
|
|
8358
|
+
status: "active",
|
|
8359
|
+
limit: OBSERVATION_INJECT_LIMIT
|
|
8360
|
+
});
|
|
8361
|
+
if (observations.length > 0) {
|
|
8362
|
+
const block = ["Consolidated beliefs (distilled from multiple stored memories; verify against sources before high-stakes use):", ...observations.map((observation) => `- ${observation.belief.slice(0, OBSERVATION_INJECT_ITEM_CHARS)} (${String(observation.proofCount)} sources)`)].join("\n").slice(0, OBSERVATION_INJECT_BLOCK_CHARS);
|
|
8363
|
+
text = `${text}\n\n${block}`;
|
|
8364
|
+
}
|
|
7921
8365
|
const dueLine = dueTotal === 0 ? "" : `\nPalace review due today: ${dueTotal}${dueTotal >= 50 ? "+" : ""} memories. Use engram_review_queue for active recall (recall beats re-reading).`;
|
|
7922
8366
|
const textWithRationale = wrapWithRationale(text, top) + dueLine;
|
|
7923
8367
|
const hash = createHash("sha256").update(textWithRationale).digest("hex");
|
|
@@ -8363,6 +8807,51 @@ function apply(ctx, config = {}) {
|
|
|
8363
8807
|
console.warn("[dsh-engram] 会话结束的收尾处理异常(不影响对话):", error);
|
|
8364
8808
|
});
|
|
8365
8809
|
});
|
|
8810
|
+
if (resolved.reflect !== "off") ctx.on("session/disposed", (session) => {
|
|
8811
|
+
const events = session.snapshotEvents();
|
|
8812
|
+
const sessionId = String(session.id);
|
|
8813
|
+
const cwd = sessionCwd(session);
|
|
8814
|
+
const route = resolved.routeOverride ?? routeFromEvents(events);
|
|
8815
|
+
const targets = [{
|
|
8816
|
+
scope: "user",
|
|
8817
|
+
open: () => openStore("user")
|
|
8818
|
+
}];
|
|
8819
|
+
if (cwd !== void 0 && existsSync(join(resolved.dbDir, dbNameForCwd(cwd)))) targets.push({
|
|
8820
|
+
scope: "project",
|
|
8821
|
+
open: () => openStoreForProjectCwd(cwd)
|
|
8822
|
+
});
|
|
8823
|
+
(async () => {
|
|
8824
|
+
for (const target of targets) try {
|
|
8825
|
+
const store = await target.open();
|
|
8826
|
+
if (resolved.reflect === "suggest") {
|
|
8827
|
+
const detail = JSON.stringify({
|
|
8828
|
+
scope: target.scope,
|
|
8829
|
+
sessionId
|
|
8830
|
+
});
|
|
8831
|
+
if (!await store.hasAudit("reflect-pending", detail)) await store.audit("reflect-pending", "AUX", detail);
|
|
8832
|
+
continue;
|
|
8833
|
+
}
|
|
8834
|
+
if (route === void 0) continue;
|
|
8835
|
+
const outcome = await reflectObservations({
|
|
8836
|
+
store,
|
|
8837
|
+
embedder: await embedder,
|
|
8838
|
+
scope: target.scope,
|
|
8839
|
+
call: (params) => streamText(ctx, {
|
|
8840
|
+
...params,
|
|
8841
|
+
sessionId: session.id
|
|
8842
|
+
}),
|
|
8843
|
+
logRequest: (data) => {
|
|
8844
|
+
store.audit("reflect-request", "AUX", JSON.stringify(data)).catch(() => {});
|
|
8845
|
+
},
|
|
8846
|
+
route,
|
|
8847
|
+
signal: AbortSignal.timeout(FINAL_INGEST_TIMEOUT_MS)
|
|
8848
|
+
});
|
|
8849
|
+
if (outcome.formed + outcome.refined + outcome.confirmed + outcome.refuted > 0) console.log(`[dsh-engram] 信念巩固完成(${target.scope}):新建 ${String(outcome.formed)},细化 ${String(outcome.refined)},维持 ${String(outcome.confirmed)},否定 ${String(outcome.refuted)}`);
|
|
8850
|
+
} catch (error) {
|
|
8851
|
+
console.warn(`[dsh-engram] 会话结束的信念巩固异常(${target.scope},不影响对话):`, error);
|
|
8852
|
+
}
|
|
8853
|
+
})();
|
|
8854
|
+
});
|
|
8366
8855
|
const runDecay = async () => {
|
|
8367
8856
|
const targets = [{
|
|
8368
8857
|
label: "user",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-engram",
|
|
3
3
|
"description": "Cross-session long-term memory for DeepSeek Harness (memory palace · AGI Architecture Exploration on dsh-market): dual-scope SQLite memory graph, hybrid retrieval, provenance audit, and a knowledge flywheel.",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.14",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"logo": "icon-256.svg",
|
|
7
7
|
"publishConfig": {
|