@modusensus/dsh-mneme 0.8.15 → 0.9.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 +21 -3
- package/assets/icon.svg +2 -2
- package/bin/cli.mjs +4 -2
- package/docs/CONFIGURATION.md +16 -3
- package/docs/CONTINUITY.md +18 -3
- package/docs/DISTILL.md +162 -0
- package/docs/STORAGE.md +11 -2
- package/docs/WORKFLOWS.md +299 -0
- package/lib/api-standalone.js +22 -39
- package/lib/api.js +294 -10
- package/lib/candidates.js +454 -0
- package/lib/capabilities.js +819 -0
- package/lib/client.js +2767 -337
- package/lib/config.js +99 -6
- package/lib/continuity.js +35 -6
- package/lib/dream/decisions.js +37 -1
- package/lib/dream/sleep.js +10 -2
- package/lib/dream.js +7 -1
- package/lib/entities/extractor.js +4 -0
- package/lib/hot-memory.js +62 -0
- package/lib/index.js +72 -18
- package/lib/inject.js +33 -64
- package/lib/maintenance.js +25 -5
- package/lib/pipeline.js +534 -0
- package/lib/recall-stats.js +22 -7
- package/lib/reclaim-http.js +150 -0
- package/lib/safe-slice.js +18 -0
- package/lib/scope.js +16 -4
- package/lib/serve.js +1 -1
- package/lib/service.js +83 -17
- package/lib/session-usage.js +350 -0
- package/lib/settings.js +19 -2
- package/lib/store.js +251 -10
- package/lib/summarize.js +239 -60
- package/lib/tools.js +11 -2
- package/package.json +1 -1
- package/scripts/publish-gate.mjs +197 -0
- package/src/api-standalone.js +22 -39
- package/src/api.js +294 -10
- package/src/candidates.js +454 -0
- package/src/capabilities.js +819 -0
- package/src/config.js +99 -6
- package/src/continuity.js +35 -6
- package/src/dream/decisions.js +37 -1
- package/src/dream/sleep.js +10 -2
- package/src/dream.js +7 -1
- package/src/entities/extractor.js +4 -0
- package/src/hot-memory.js +62 -0
- package/src/index.js +72 -18
- package/src/inject.js +33 -64
- package/src/maintenance.js +25 -5
- package/src/pipeline.js +534 -0
- package/src/recall-stats.js +22 -7
- package/src/reclaim-http.js +150 -0
- package/src/safe-slice.js +18 -0
- package/src/scope.js +16 -4
- package/src/serve.js +1 -1
- package/src/service.js +83 -17
- package/src/session-usage.js +350 -0
- package/src/settings.js +19 -2
- package/src/store.js +251 -10
- package/src/summarize.js +239 -60
- package/src/tools.js +11 -2
- package/test/api-routes-gaps.test.js +681 -0
- package/test/api.test.js +171 -8
- package/test/candidates.test.js +345 -0
- package/test/capabilities.test.js +577 -0
- package/test/client.test.js +715 -4
- package/test/continuity.test.js +84 -0
- package/test/contrast.test.js +314 -0
- package/test/dream-merge-guardrail.test.js +219 -0
- package/test/embed-ready-poll.test.js +94 -0
- package/test/entities.test.js +13 -2
- package/test/failure-query.test.js +147 -0
- package/test/failure-retention.test.js +105 -0
- package/test/graph-api.test.js +36 -2
- package/test/hot-memory.test.js +67 -1
- package/test/inject-recall.test.js +68 -1
- package/test/inject.test.js +23 -0
- package/test/llm-audit.test.js +190 -1
- package/test/maintenance.test.js +112 -2
- package/test/mint-source.test.js +202 -0
- package/test/pipeline.test.js +505 -0
- package/test/placement.test.js +1 -1
- package/test/pre-inject-gate.test.js +10 -0
- package/test/publish-gate.test.js +272 -0
- package/test/recall-retention.test.js +128 -0
- package/test/recall-stats.test.js +89 -4
- package/test/reclaim-http.test.js +209 -0
- package/test/release-workflow.test.js +77 -0
- package/test/scope-distill-stamp.test.js +135 -0
- package/test/serve.test.js +1 -1
- package/test/service.test.js +7 -2
- package/test/session-usage.test.js +308 -0
- package/test/settings.test.js +8 -0
- package/test/store-write-path-columns.test.js +191 -0
- package/test/store.test.js +108 -0
- package/test/summarize.test.js +350 -0
- package/test/supersede-state-tracking.test.js +4 -4
- package/test/tools.test.js +16 -1
package/lib/inject.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createScopeResolver } from "./scope.js";
|
|
2
|
-
import { createHotMemory } from "./hot-memory.js";
|
|
2
|
+
import { createHotMemory, roundsFromEvents } from "./hot-memory.js";
|
|
3
3
|
import { STR, langOf } from "./lang.js";
|
|
4
4
|
import { MEMORY_GUIDE_SECTION } from "./guide.js";
|
|
5
5
|
import { adaptiveInjectBudget } from "./search/adaptive.js";
|
|
6
6
|
import { injectChildEnabled } from "./config.js";
|
|
7
|
+
import { safeSlice } from "./safe-slice.js";
|
|
7
8
|
|
|
8
9
|
// Issue #179:注入预览的数据底座。systemPrompt 渲染是同步回调,面板只能事后
|
|
9
10
|
// 拉取,所以在这里旁路缓存「最近一帧组装」——快照就是本次渲染用过的同一份
|
|
@@ -16,21 +17,19 @@ export function getInjectionSnapshot() {
|
|
|
16
17
|
return injectionSnapshot;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (tail >= 0xd800 && tail <= 0xdbff) cut = cut.slice(0, -1);
|
|
33
|
-
return cut;
|
|
20
|
+
// 「模型可见事件 seq」集合:宿主在 session.surface.nodes 维护可见面——rewind 的
|
|
21
|
+
// surfaceOp replace 与 compact 的隐藏替换都作用在这层(dsh-rewind 插件即据此计算
|
|
22
|
+
// 替换区间)。拿不到(老版本宿主)返回 null,调用方退回「全量事件」的旧行为。
|
|
23
|
+
// 读取必须在 try 内:`surface.nodes` 是宿主 getter,其 _assertProjections 在消息投影
|
|
24
|
+
// 所属插件被移除时会 throw(dsh-session lib/types/surface.js)。抛出等同于「没有
|
|
25
|
+
// 可见面」,绝不能让整块热记忆消失——那是比旧偏差更糟的退化。
|
|
26
|
+
function visibleSeqsOf(session) {
|
|
27
|
+
try {
|
|
28
|
+
const nodes = session?.surface?.nodes;
|
|
29
|
+
return Array.isArray(nodes) ? new Set(nodes) : null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
34
33
|
}
|
|
35
34
|
|
|
36
35
|
// Best-effort extraction of the current user's latest message text from the
|
|
@@ -39,15 +38,23 @@ function safeSlice(text, limit) {
|
|
|
39
38
|
// session event log (same event shape summarize.js consumes) and returns the
|
|
40
39
|
// most recent human message. Any failure degrades to "" — the injector then
|
|
41
40
|
// falls back to the legacy rule-based pick, never breaking the render.
|
|
42
|
-
|
|
41
|
+
//
|
|
42
|
+
// v0.9.0:导出给 tools.js 的 memory_update 用。失败记录表的 query 与注入
|
|
43
|
+
// 候选问的是同一件事——「当时到底想干什么」;这条取原话的路只该有一份实现。
|
|
44
|
+
// 复制一份的代价本仓库付过几次(src/lib 平价、snapshotEvents 兼容层),所以
|
|
45
|
+
// 这里只加 export,不改行为。
|
|
46
|
+
export function lastUserQuery(ctx) {
|
|
43
47
|
try {
|
|
44
48
|
const session = ctx?.agent?.session;
|
|
45
49
|
// DSH ≥0.1.2-rc only exposes events via snapshotEvents(); older builds
|
|
46
50
|
// still have the .events property, so fall back to it.
|
|
47
51
|
const events = session?.snapshotEvents?.() ?? session?.events;
|
|
48
52
|
if (!Array.isArray(events) || events.length === 0) return "";
|
|
53
|
+
const visibleSeqs = visibleSeqsOf(session);
|
|
49
54
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
50
55
|
const event = events[i];
|
|
56
|
+
// 被撤回/被压缩遮蔽的事件不算「最近一条用户消息」(与 extractRounds 同口径)。
|
|
57
|
+
if (visibleSeqs && !visibleSeqs.has(event?.seq)) continue;
|
|
51
58
|
if (event?.type !== "user/message") continue;
|
|
52
59
|
const kind = event.data?.source?.kind;
|
|
53
60
|
if (kind !== undefined && kind !== "user") continue;
|
|
@@ -66,58 +73,20 @@ function lastUserQuery(ctx) {
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
// Hot-memory round extraction (v0.5.0 1.3): pairs each user/message with the
|
|
69
|
-
// next assistant reply
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
76
|
+
// next assistant reply. The pairing lives in hot-memory.js (roundsFromEvents)
|
|
77
|
+
// so it can be unit-tested directly; this adapter only resolves the session's
|
|
78
|
+
// raw event log and the model-visible seq set (session.surface.nodes — see
|
|
79
|
+
// visibleSeqsOf). Rewind-shadowed / compacted events are thereby excluded:
|
|
80
|
+
// hot memory and the model see the same conversation. Best-effort: returns []
|
|
81
|
+
// on any failure, and the hot block simply does not render.
|
|
73
82
|
function extractRounds(ctx, maxRounds) {
|
|
74
83
|
try {
|
|
75
84
|
const session = ctx?.agent?.session;
|
|
85
|
+
// DSH ≥0.1.2-rc only exposes events via snapshotEvents(); older builds
|
|
86
|
+
// still have the .events property, so fall back to it.
|
|
76
87
|
const events = session?.snapshotEvents?.() ?? session?.events;
|
|
77
88
|
if (!Array.isArray(events) || events.length === 0) return [];
|
|
78
|
-
|
|
79
|
-
let pendingQuery = null;
|
|
80
|
-
const textOf = (event) => {
|
|
81
|
-
// user/message 的正文就在 data 上,而 assistant/message(以及 tool/result)的消息体
|
|
82
|
-
// 嵌在 data.message 下 —— DSH 侧由 @deepseek-ai/dsh-session 的
|
|
83
|
-
// assertMessageEventShape 一行写死:const message = type === "user/message" ? record : record?.["message"]。
|
|
84
|
-
// 只读 data.content 会让每一轮的 response 恒为空(Issue #129)。
|
|
85
|
-
// 这里用 ?? 兜底而不是直接改成 data.message.content:user 消息与旧的扁平形状都照旧可用。
|
|
86
|
-
const parts = event?.data?.content ?? event?.data?.message?.content;
|
|
87
|
-
if (!Array.isArray(parts)) return "";
|
|
88
|
-
// 只取正文 part(type === "text" 或字符串),跳过 reasoning(issue #162):
|
|
89
|
-
// reasoning 里出现 Go template / Vue / Handlebars / 正则 / 日志原文的概率远高于
|
|
90
|
-
// 正文,把它带进 hot memory 会让 `{{...}}` 模板片段注入 prompt。旧的扁平形状
|
|
91
|
-
// (字符串 part)照旧取用,无 type 字段的兜底视为正文。
|
|
92
|
-
return parts
|
|
93
|
-
.map((p) => {
|
|
94
|
-
if (typeof p === "string") return p;
|
|
95
|
-
if (p?.type && p.type !== "text") return "";
|
|
96
|
-
return p?.text ?? "";
|
|
97
|
-
})
|
|
98
|
-
.filter(Boolean)
|
|
99
|
-
.join("\n")
|
|
100
|
-
.trim();
|
|
101
|
-
};
|
|
102
|
-
for (const event of events) {
|
|
103
|
-
const kind = event?.data?.source?.kind;
|
|
104
|
-
const isUser = event?.type === "user/message" && (kind === undefined || kind === "user");
|
|
105
|
-
if (isUser) {
|
|
106
|
-
if (pendingQuery) rounds.push({ query: pendingQuery, response: "" });
|
|
107
|
-
pendingQuery = safeSlice(textOf(event), 500);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
// Only assistant-originated events close a round; tool/system events
|
|
111
|
-
// carrying text must not be mistaken for the model's reply.
|
|
112
|
-
const isAssistant = typeof event?.type === "string" && event.type.includes("assistant")
|
|
113
|
-
|| kind === "assistant";
|
|
114
|
-
const body = isAssistant ? textOf(event) : "";
|
|
115
|
-
if (!body || !pendingQuery) continue;
|
|
116
|
-
rounds.push({ query: pendingQuery, response: safeSlice(body, 800) });
|
|
117
|
-
pendingQuery = null;
|
|
118
|
-
}
|
|
119
|
-
if (pendingQuery) rounds.push({ query: pendingQuery, response: "" });
|
|
120
|
-
return rounds.slice(-maxRounds);
|
|
89
|
+
return roundsFromEvents(events, maxRounds, visibleSeqsOf(session));
|
|
121
90
|
} catch {
|
|
122
91
|
return [];
|
|
123
92
|
}
|
package/lib/maintenance.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 东西)。所以模块里没有任何定时器,挂点只有两个:standalone 数据面的
|
|
7
7
|
// `POST /maintenance/reclaim` 与它上面的 `dsh-mneme reclaim` 子命令。
|
|
8
8
|
//
|
|
9
|
-
//
|
|
9
|
+
// 三项都零价值判断、零条数变化:
|
|
10
10
|
// A `dream_runs.input` 置空。这一列的含义按 run_type 分叉:auto / sleep 的 run 存的
|
|
11
11
|
// 是当时的记忆库快照,可由记忆库重建;**organize 的 run 存的是 apply 的重放载荷**,
|
|
12
12
|
// 置空会让那次 apply 静默空转并把报告锁死,所以 organize 行不在可清范围内(谓词
|
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
// 与 receipt 一律保留,行永不删(拍板 3:裁列同意、删行不同意)。
|
|
15
15
|
// B 归档行向量置空:检索 SQL 恒带 `archived = 0`,这部分向量按定义不可达。它不是
|
|
16
16
|
// 单程票——取消归档时 service 会重新排队嵌入(archiveMemory 的还原分支)。
|
|
17
|
+
// C #399 蒸馏原文记录按 distilled_at 到期删除:记录是蒸馏那次调用的凭证,越旧越
|
|
18
|
+
// 没有复核价值,而它按定义就是整段对话原文(本入口里唯一的「真删内容」项)。它的
|
|
19
|
+
// 常态保留期是独立配置键 distillRecords.retentionDays(启动期裁剪走那个键);本入口
|
|
20
|
+
// **不读那个键**,与 A/B 共用同一条 --older-than 截止(想对齐就显式传同样的天数,
|
|
21
|
+
// 见 STORAGE.md「两处不冲突」)。链接
|
|
22
|
+
// (memory_provenance)**不在可清范围内**:保留指向空区间即「来源已过期」,删链
|
|
23
|
+
// 接等于假装这条记忆从来没有过出处——那是篡改历史,不是回收。
|
|
17
24
|
//
|
|
18
25
|
// 报告口径(拍板 2):收益按 VACUUM 前后体积量,不按列字节估——实际释放来自溢出页与
|
|
19
26
|
// 索引页回收,列文本大小只是上界,所以两个数分开报,不混成一个「省了多少」。
|
|
@@ -33,7 +40,8 @@ export const DEFAULT_INPUT_RETENTION_DAYS = 7;
|
|
|
33
40
|
*
|
|
34
41
|
* @param {object} deps
|
|
35
42
|
* @param {object} deps.store 存储层(dreamRunInputStats / clearDreamRunInputs /
|
|
36
|
-
* archivedEmbeddingStats / clearArchivedEmbeddings /
|
|
43
|
+
* archivedEmbeddingStats / clearArchivedEmbeddings / distillRecordStats /
|
|
44
|
+
* clearDistillRecords / storageStats / vacuum / saveLlmAudit)
|
|
37
45
|
* @param {object} [deps.config] 已解析配置(读 llmAudit.enabled:关掉 audit 时不落 receipt)
|
|
38
46
|
* @param {object} [deps.logger]
|
|
39
47
|
* @param {() => number} [deps.now] 时钟注入(测试用)
|
|
@@ -86,15 +94,26 @@ export function createMaintenance({ store, config, logger, now = Date.now } = {}
|
|
|
86
94
|
const before = store.storageStats();
|
|
87
95
|
const inputs = store.dreamRunInputStats(cutoff);
|
|
88
96
|
const embeddings = store.archivedEmbeddingStats();
|
|
97
|
+
// #399:蒸馏原文记录与上面两项同款——按 distilled_at 到期裁剪。链接
|
|
98
|
+
// (memory_provenance)不在此列:它保留指向空区间,显示「来源已过期」。
|
|
99
|
+
const records = store.distillRecordStats(cutoff);
|
|
89
100
|
const report = {
|
|
90
101
|
dryRun,
|
|
91
102
|
olderThanDays: days,
|
|
92
103
|
cutoff,
|
|
93
104
|
// planned = 这次会动几行;cleared = 实际动了几行(dry-run 恒 0)
|
|
94
|
-
planned: {
|
|
105
|
+
planned: {
|
|
106
|
+
dream_run_inputs: inputs.runs,
|
|
107
|
+
archived_embeddings: embeddings.rows,
|
|
108
|
+
distill_records: records.rows
|
|
109
|
+
},
|
|
95
110
|
// 列文本大小:上界,不是回收量(真实释放看下面的 size 差值)
|
|
96
|
-
column_bytes: {
|
|
97
|
-
|
|
111
|
+
column_bytes: {
|
|
112
|
+
dream_run_inputs: inputs.bytes,
|
|
113
|
+
archived_embeddings: embeddings.bytes,
|
|
114
|
+
distill_records: records.bytes
|
|
115
|
+
},
|
|
116
|
+
cleared: { dream_run_inputs: 0, archived_embeddings: 0, distill_records: 0 },
|
|
98
117
|
size: {
|
|
99
118
|
before_bytes: before.fileBytes,
|
|
100
119
|
after_bytes: before.fileBytes,
|
|
@@ -114,6 +133,7 @@ export function createMaintenance({ store, config, logger, now = Date.now } = {}
|
|
|
114
133
|
}
|
|
115
134
|
report.cleared.dream_run_inputs = store.clearDreamRunInputs(cutoff);
|
|
116
135
|
report.cleared.archived_embeddings = store.clearArchivedEmbeddings();
|
|
136
|
+
report.cleared.distill_records = store.clearDistillRecords(cutoff);
|
|
117
137
|
if (report.vacuum.requested) {
|
|
118
138
|
// VACUUM 失败不能把前面的清理一起吞掉:两个 UPDATE 已经各自提交了,此时抛出去
|
|
119
139
|
// 就是「数据已经不可逆地清了,但没有任何回执」。所以失败记进报告、回执照写,
|