@modusensus/dsh-mneme 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +415 -152
- package/{dsh-mneme/lib → lib}/client.js +55 -8
- package/{dsh-mneme/src → lib}/config.js +5 -0
- package/{dsh-mneme/lib → lib}/inject.js +31 -2
- package/package.json +40 -18
- package/{dsh-mneme/lib → src}/config.js +5 -0
- package/{dsh-mneme/src → src}/inject.js +31 -2
- package/{dsh-mneme/test → test}/client.test.js +37 -0
- package/{dsh-mneme/test → test}/config.test.js +7 -0
- package/{dsh-mneme/test → test}/inject.test.js +28 -0
- package/.github/workflows/test.yml +0 -32
- package/CHANGELOG.md +0 -89
- package/CONTRIBUTING.md +0 -245
- package/SECURITY.md +0 -544
- package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
- package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
- package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
- package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
- package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
- package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
- package/dsh-mneme/CHANGELOG.md +0 -295
- package/dsh-mneme/LICENSE +0 -21
- package/dsh-mneme/README.md +0 -484
- package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
- package/dsh-mneme/docs/ENTITIES.md +0 -245
- package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
- package/dsh-mneme/docs/MIGRATION.md +0 -127
- package/dsh-mneme/docs/SEMANTIC.md +0 -256
- package/dsh-mneme/docs/SLEEP.md +0 -163
- package/dsh-mneme/package-lock.json +0 -1936
- package/dsh-mneme/package.json +0 -80
- package/v0.7-plan-2026-08-21.md +0 -75
- package//346/250/252/345/271/205.png +0 -0
- /package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +0 -0
- /package/{dsh-mneme/lib → lib}/api.js +0 -0
- /package/{dsh-mneme/lib → lib}/commands.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/decisions.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/sleep.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream/tag-extractor.js +0 -0
- /package/{dsh-mneme/lib → lib}/dream.js +0 -0
- /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
- /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
- /package/{dsh-mneme/lib → lib}/heat.js +0 -0
- /package/{dsh-mneme/lib → lib}/hot-memory.js +0 -0
- /package/{dsh-mneme/lib → lib}/index.js +0 -0
- /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
- /package/{dsh-mneme/lib → lib}/mirror.js +0 -0
- /package/{dsh-mneme/lib → lib}/parser/tag.js +0 -0
- /package/{dsh-mneme/lib → lib}/parser/wiki-link.js +0 -0
- /package/{dsh-mneme/lib → lib}/quality-filter.js +0 -0
- /package/{dsh-mneme/lib → lib}/reranker.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
- /package/{dsh-mneme/lib → lib}/search/tag-boost.js +0 -0
- /package/{dsh-mneme/lib → lib}/service.js +0 -0
- /package/{dsh-mneme/lib → lib}/settings.js +0 -0
- /package/{dsh-mneme/lib → lib}/store.js +0 -0
- /package/{dsh-mneme/lib → lib}/summarize.js +0 -0
- /package/{dsh-mneme/lib → lib}/tools.js +0 -0
- /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
- /package/{dsh-mneme/scripts → scripts}/sync-lib.js +0 -0
- /package/{dsh-mneme/src → src}/api.js +0 -0
- /package/{dsh-mneme/src → src}/commands.js +0 -0
- /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
- /package/{dsh-mneme/src → src}/dream/decisions.js +0 -0
- /package/{dsh-mneme/src → src}/dream/sleep.js +0 -0
- /package/{dsh-mneme/src → src}/dream/tag-extractor.js +0 -0
- /package/{dsh-mneme/src → src}/dream.js +0 -0
- /package/{dsh-mneme/src → src}/embedding.js +0 -0
- /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
- /package/{dsh-mneme/src → src}/heat.js +0 -0
- /package/{dsh-mneme/src → src}/hot-memory.js +0 -0
- /package/{dsh-mneme/src → src}/index.js +0 -0
- /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
- /package/{dsh-mneme/src → src}/mirror.js +0 -0
- /package/{dsh-mneme/src → src}/parser/tag.js +0 -0
- /package/{dsh-mneme/src → src}/parser/wiki-link.js +0 -0
- /package/{dsh-mneme/src → src}/quality-filter.js +0 -0
- /package/{dsh-mneme/src → src}/reranker.js +0 -0
- /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
- /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
- /package/{dsh-mneme/src → src}/search/tag-boost.js +0 -0
- /package/{dsh-mneme/src → src}/service.js +0 -0
- /package/{dsh-mneme/src → src}/settings.js +0 -0
- /package/{dsh-mneme/src → src}/store.js +0 -0
- /package/{dsh-mneme/src → src}/summarize.js +0 -0
- /package/{dsh-mneme/src → src}/tools.js +0 -0
- /package/{dsh-mneme/src → src}/vector-index.js +0 -0
- /package/{dsh-mneme/test → test}/api.test.js +0 -0
- /package/{dsh-mneme/test → test}/audit.test.js +0 -0
- /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
- /package/{dsh-mneme/test → test}/boundary-v0625.test.js +0 -0
- /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
- /package/{dsh-mneme/test → test}/commands.test.js +0 -0
- /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
- /package/{dsh-mneme/test → test}/directory.test.js +0 -0
- /package/{dsh-mneme/test → test}/dream.test.js +0 -0
- /package/{dsh-mneme/test → test}/entities.test.js +0 -0
- /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
- /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
- /package/{dsh-mneme/test → test}/graph-api.test.js +0 -0
- /package/{dsh-mneme/test → test}/heat.test.js +0 -0
- /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
- /package/{dsh-mneme/test → test}/hot-memory.test.js +0 -0
- /package/{dsh-mneme/test → test}/llm-audit.test.js +0 -0
- /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
- /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
- /package/{dsh-mneme/test → test}/normalize-decisions.test.js +0 -0
- /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
- /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
- /package/{dsh-mneme/test → test}/provenance.test.js +0 -0
- /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
- /package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-layer.test.js +0 -0
- /package/{dsh-mneme/test → test}/recall-runs.test.js +0 -0
- /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
- /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
- /package/{dsh-mneme/test → test}/reranker.test.js +0 -0
- /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
- /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
- /package/{dsh-mneme/test → test}/service-search.test.js +0 -0
- /package/{dsh-mneme/test → test}/service.test.js +0 -0
- /package/{dsh-mneme/test → test}/settings.test.js +0 -0
- /package/{dsh-mneme/test → test}/sleep-heat.test.js +0 -0
- /package/{dsh-mneme/test → test}/sleep.test.js +0 -0
- /package/{dsh-mneme/test → test}/store.test.js +0 -0
- /package/{dsh-mneme/test → test}/stress.test.js +0 -0
- /package/{dsh-mneme/test → test}/summarize.test.js +0 -0
- /package/{dsh-mneme/test → test}/tag-boost.test.js +0 -0
- /package/{dsh-mneme/test → test}/tag.test.js +0 -0
- /package/{dsh-mneme/test → test}/tools.test.js +0 -0
- /package/{dsh-mneme/test → test}/updated-at-semantics.test.js +0 -0
- /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
- /package/{dsh-mneme/test → test}/wiki-link.test.js +0 -0
|
@@ -122,6 +122,8 @@ window.__ModuleLoader__.load({
|
|
|
122
122
|
"memory.directory.empty": "暂无记忆条目",
|
|
123
123
|
"directory.editToggle": "编辑模式(显示删除)",
|
|
124
124
|
"directory.deleteConfirm": "确定删除这条记忆吗?",
|
|
125
|
+
"directory.deleteConfirmShort": "确认?",
|
|
126
|
+
"directory.deleteError": "删除失败",
|
|
125
127
|
"memory.card.open": "在主区记忆库中查看全文",
|
|
126
128
|
"memory.time.now": "刚刚",
|
|
127
129
|
"memory.time.seconds": "{n}秒前",
|
|
@@ -229,6 +231,8 @@ window.__ModuleLoader__.load({
|
|
|
229
231
|
"memory.directory.empty": "No memories yet",
|
|
230
232
|
"directory.editToggle": "Edit mode",
|
|
231
233
|
"directory.deleteConfirm": "Delete this memory?",
|
|
234
|
+
"directory.deleteConfirmShort": "Confirm?",
|
|
235
|
+
"directory.deleteError": "Delete failed",
|
|
232
236
|
"memory.card.open": "Open full text in the Memory tab",
|
|
233
237
|
"memory.time.now": "just now",
|
|
234
238
|
"memory.time.seconds": "{n}s ago",
|
|
@@ -432,6 +436,9 @@ window.__ModuleLoader__.load({
|
|
|
432
436
|
".mneme-dir-del{margin-left:auto;background:none;border:none;color:#e5484d;cursor:pointer;font-size:13px;line-height:1;padding:2px 4px}",
|
|
433
437
|
".mneme-dir-del:hover{filter:brightness(1.25)}",
|
|
434
438
|
".mneme-edit-on .mneme-dir-del{opacity:1}",
|
|
439
|
+
".mneme-dir-del-confirm{font-weight:700;background:rgba(229,72,77,.12);border-radius:4px}",
|
|
440
|
+
".mneme-dir-item.mneme-dir-error{background:rgba(229,72,77,.14)}",
|
|
441
|
+
".mneme-dir-del-err{flex:none;font-size:12px;line-height:16px;color:#e5484d}",
|
|
435
442
|
".mneme-dir-ititle{background:none;border:none;padding:0;font:inherit;color:inherit;cursor:pointer;flex:1;text-align:left;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
436
443
|
".mneme-dir-folder{margin-bottom:4px}",
|
|
437
444
|
".mneme-dir-folderhead{display:flex;align-items:center;gap:6px;width:100%;padding:6px 8px;border:none;border-radius:8px;background:none;color:var(--dsw-alias-label-primary);cursor:pointer;font-family:inherit;font-size:13px;font-weight:500;line-height:18px;text-align:left}",
|
|
@@ -1184,6 +1191,9 @@ window.__ModuleLoader__.load({
|
|
|
1184
1191
|
const [dir, setDir] = useState(null); // { groups: [...], untagged: [...] }
|
|
1185
1192
|
const [status, setStatus] = useState("loading"); // loading | ready | error
|
|
1186
1193
|
const [editMode, setEditMode] = useState(false); // manual tag/delete UI gate
|
|
1194
|
+
const [confirmingId, setConfirmingId] = useState(null); // row waiting for the 2nd confirm click
|
|
1195
|
+
const [delErrorId, setDelErrorId] = useState(null); // row whose delete just failed
|
|
1196
|
+
const confirmTimer = useRef(null);
|
|
1187
1197
|
|
|
1188
1198
|
useEffect(() => {
|
|
1189
1199
|
let cancelled = false;
|
|
@@ -1215,9 +1225,24 @@ window.__ModuleLoader__.load({
|
|
|
1215
1225
|
}).then((res) => { if (res.ok) setEditMode(next); }).catch(() => {});
|
|
1216
1226
|
};
|
|
1217
1227
|
|
|
1228
|
+
const resetConfirm = () => {
|
|
1229
|
+
clearTimeout(confirmTimer.current);
|
|
1230
|
+
setConfirmingId(null);
|
|
1231
|
+
};
|
|
1232
|
+
|
|
1218
1233
|
const handleDelete = (memId, e) => {
|
|
1219
1234
|
e?.stopPropagation?.();
|
|
1220
|
-
|
|
1235
|
+
// 第一击进入「确认?」态,第二击才真正发 DELETE;宿主 web 环境里
|
|
1236
|
+
// window.confirm 不可靠(可能被拦截或直接返回 false),点击会无反应,
|
|
1237
|
+
// 所以改为面板内联两步确认,不依赖任何原生对话框。
|
|
1238
|
+
if (confirmingId !== memId) {
|
|
1239
|
+
setConfirmingId(memId);
|
|
1240
|
+
clearTimeout(confirmTimer.current);
|
|
1241
|
+
confirmTimer.current = setTimeout(() => setConfirmingId((cur) => (cur === memId ? null : cur)), 3000);
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
clearTimeout(confirmTimer.current);
|
|
1245
|
+
setConfirmingId(null);
|
|
1221
1246
|
apiFetch("/api/dsh-mneme/memories", {
|
|
1222
1247
|
method: "DELETE",
|
|
1223
1248
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1225,26 +1250,48 @@ window.__ModuleLoader__.load({
|
|
|
1225
1250
|
})
|
|
1226
1251
|
.then((res) => (res.ok ? res.json() : null))
|
|
1227
1252
|
.then((d) => {
|
|
1228
|
-
if (!d || d.deleted !== true)
|
|
1253
|
+
if (!d || d.deleted !== true) {
|
|
1254
|
+
// 服务端未确认删除也给出可见提示,不静默吞掉
|
|
1255
|
+
setDelErrorId(memId);
|
|
1256
|
+
setTimeout(() => setDelErrorId((cur) => (cur === memId ? null : cur)), 3500);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1229
1259
|
setDir((prev) => ({
|
|
1230
1260
|
groups: prev.groups.map((g) => ({ ...g, memories: g.memories.filter((m) => m.id !== memId) })),
|
|
1231
1261
|
untagged: prev.untagged.filter((m) => m.id !== memId)
|
|
1232
1262
|
}));
|
|
1233
1263
|
})
|
|
1234
|
-
.catch(() => {
|
|
1264
|
+
.catch(() => {
|
|
1265
|
+
// 网络失败同样给出可见提示
|
|
1266
|
+
setDelErrorId(memId);
|
|
1267
|
+
setTimeout(() => setDelErrorId((cur) => (cur === memId ? null : cur)), 3500);
|
|
1268
|
+
});
|
|
1235
1269
|
};
|
|
1236
1270
|
|
|
1237
1271
|
const renderFolder = (name, memories, extraKey, emptyLabel) => {
|
|
1238
1272
|
const folderKey = extraKey || name;
|
|
1239
1273
|
const open = !collapsed[folderKey];
|
|
1240
|
-
const rows = memories.map((m) =>
|
|
1241
|
-
|
|
1274
|
+
const rows = memories.map((m) => {
|
|
1275
|
+
const confirming = confirmingId === m.id;
|
|
1276
|
+
const err = delErrorId === m.id;
|
|
1277
|
+
return h("div", {
|
|
1278
|
+
key: m.id,
|
|
1279
|
+
className: err ? "mneme-dir-item mneme-dir-error" : "mneme-dir-item",
|
|
1280
|
+
// 点击行内其它地方取消「确认?」态
|
|
1281
|
+
onClick: resetConfirm
|
|
1282
|
+
},
|
|
1242
1283
|
h("span", { className: "mneme-tree-line" }),
|
|
1243
1284
|
h("button", { type: "button", className: "mneme-dir-ititle", onClick: () => onJump(m) }, m.title || m.content?.slice(0, 40)),
|
|
1244
1285
|
h("span", { className: "mneme-dir-itime" }, formatDate(m.updated_at || m.created_at)),
|
|
1245
|
-
editMode && h("button", {
|
|
1246
|
-
|
|
1247
|
-
|
|
1286
|
+
editMode && h("button", {
|
|
1287
|
+
type: "button",
|
|
1288
|
+
className: confirming ? "mneme-dir-del mneme-dir-del-confirm" : "mneme-dir-del",
|
|
1289
|
+
"aria-label": "delete",
|
|
1290
|
+
onClick: (e) => handleDelete(m.id, e)
|
|
1291
|
+
}, confirming ? t("directory.deleteConfirmShort") : "✕"),
|
|
1292
|
+
err && h("span", { className: "mneme-dir-del-err" }, t("directory.deleteError"))
|
|
1293
|
+
);
|
|
1294
|
+
});
|
|
1248
1295
|
return h("div", {
|
|
1249
1296
|
key: folderKey,
|
|
1250
1297
|
className: "mneme-dir-folder",
|
|
@@ -17,6 +17,11 @@ export const Config = z.object({
|
|
|
17
17
|
summarizeModel: z.string().default(""),
|
|
18
18
|
maxInjectedItems: z.natural().min(1).max(20).default(5),
|
|
19
19
|
importanceThreshold: z.natural().min(1).max(5).default(3),
|
|
20
|
+
// 时间前缀注入(issue #34,opt-in,默认关):开启时,新对话开始时把当前
|
|
21
|
+
// 日期时间以 [当前时间: 2026-08-26 周二 19:30] 前缀注入一次,让模型感知
|
|
22
|
+
// "现在几点/周几"。只注入一次(对话开始时),同会话后续渲染不再重复;
|
|
23
|
+
// 关闭时行为与之前完全一致。
|
|
24
|
+
injectTimePrefix: z.boolean().default(false),
|
|
20
25
|
autoDream: z.boolean().default(true),
|
|
21
26
|
dreamThresholdCount: z.natural().min(1).max(1000).default(10),
|
|
22
27
|
dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
|
|
@@ -75,6 +75,32 @@ export function createInjector(ctx, service, settings, config) {
|
|
|
75
75
|
const maxItems = config.maxInjectedItems ?? 5;
|
|
76
76
|
const threshold = config.importanceThreshold ?? 3;
|
|
77
77
|
|
|
78
|
+
// Time-prefix injection (issue #34): opt-in, off by default. When enabled the
|
|
79
|
+
// current date/time is injected once per conversation — at the first prompt
|
|
80
|
+
// assembly of a new session — so the model can sense what time/day it is.
|
|
81
|
+
// A per-session latch means later assemblies in the same session never
|
|
82
|
+
// re-inject; a bare render ctx (no session id) falls back to once per
|
|
83
|
+
// injector lifetime.
|
|
84
|
+
const WEEKDAYS = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
|
85
|
+
let timePrefixSession = null;
|
|
86
|
+
|
|
87
|
+
function renderTimePrefix(ctx) {
|
|
88
|
+
if (config.injectTimePrefix !== true) return "";
|
|
89
|
+
const sessionId = ctx?.agent?.session?.id;
|
|
90
|
+
if (sessionId === undefined) {
|
|
91
|
+
if (timePrefixSession !== null) return "";
|
|
92
|
+
timePrefixSession = true;
|
|
93
|
+
} else {
|
|
94
|
+
if (sessionId === timePrefixSession) return "";
|
|
95
|
+
timePrefixSession = sessionId;
|
|
96
|
+
}
|
|
97
|
+
const now = new Date();
|
|
98
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
99
|
+
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
100
|
+
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
101
|
+
return `[当前时间: ${date} ${WEEKDAYS[now.getDay()]} ${time}]`;
|
|
102
|
+
}
|
|
103
|
+
|
|
78
104
|
// Bug6: bound the injected memory block. Each entry's content is truncated to
|
|
79
105
|
// MAX_CONTENT chars (trailing `…`); the whole block gets a MAX_BLOCK budget
|
|
80
106
|
// and an entry that would exceed it collapses to its title only, so a long
|
|
@@ -188,8 +214,11 @@ export function createInjector(ctx, service, settings, config) {
|
|
|
188
214
|
// separate context) keeps the prompt assembly stable at two blocks.
|
|
189
215
|
const hotText = renderHotContext(ctx);
|
|
190
216
|
const body = render(candidates);
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
let out = !hotText ? body : (body ? `${hotText}\n\n${body}` : hotText);
|
|
218
|
+
// Time prefix (issue #34): leads the injection block once per session.
|
|
219
|
+
const timePrefix = renderTimePrefix(ctx);
|
|
220
|
+
if (!timePrefix) return out;
|
|
221
|
+
return out ? `${timePrefix}\n\n${out}` : timePrefix;
|
|
193
222
|
}
|
|
194
223
|
}),
|
|
195
224
|
ctx.systemPrompt.context({
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modusensus/dsh-mneme",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"description": "Cross-session memory plugin for DeepSeek Harness with autoDream consolidation: SQLite store, Markdown mirrors, 7 model tools, automatic injection, session summarization, user profile/rules, custom slash commands, vector (semantic) search, and a Web GUI panel",
|
|
4
|
+
"version": "0.7.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -12,16 +12,23 @@
|
|
|
12
12
|
"url": "https://github.com/modusensus/dsh-mneme/issues"
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
|
-
"main": "
|
|
15
|
+
"main": "lib/index.js",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
|
-
"default": "./
|
|
18
|
+
"default": "./lib/index.js"
|
|
19
19
|
},
|
|
20
20
|
"./client": {
|
|
21
|
-
"default": "./
|
|
21
|
+
"default": "./lib/client.js"
|
|
22
22
|
},
|
|
23
|
-
"./package.json": "./
|
|
23
|
+
"./package.json": "./package.json"
|
|
24
24
|
},
|
|
25
|
+
"files": [
|
|
26
|
+
"lib",
|
|
27
|
+
"src",
|
|
28
|
+
"scripts",
|
|
29
|
+
"test",
|
|
30
|
+
"cordis.patch.yml"
|
|
31
|
+
],
|
|
25
32
|
"dsh": {
|
|
26
33
|
"client": {
|
|
27
34
|
"inject": [
|
|
@@ -33,20 +40,9 @@
|
|
|
33
40
|
"platform": "web"
|
|
34
41
|
},
|
|
35
42
|
"bundle": {
|
|
36
|
-
"patch": "./
|
|
37
|
-
},
|
|
38
|
-
"marketplace": {
|
|
39
|
-
"profiles": [
|
|
40
|
-
"web"
|
|
41
|
-
],
|
|
42
|
-
"requiresBuildApproval": false,
|
|
43
|
-
"requiresRestart": true,
|
|
44
|
-
"manualSteps": false
|
|
43
|
+
"patch": "./cordis.patch.yml"
|
|
45
44
|
}
|
|
46
45
|
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"prepublishOnly": "node -e \"const fs=require('fs');const v=JSON.parse(fs.readFileSync('./dsh-mneme/package.json','utf8')).version;const p=JSON.parse(fs.readFileSync('./package.json','utf8'));p.version=v;fs.writeFileSync('./package.json',JSON.stringify(p,null,2)+'\\n')\""
|
|
49
|
-
},
|
|
50
46
|
"peerDependencies": {
|
|
51
47
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
52
48
|
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
@@ -54,5 +50,31 @@
|
|
|
54
50
|
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
|
|
55
51
|
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
56
52
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
56
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
|
|
57
|
+
"@deepseek-ai/dsh-llm": "^0.1.0-rc.6",
|
|
58
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
|
|
59
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
60
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
61
|
+
"c8": "^12.0.0"
|
|
62
|
+
},
|
|
63
|
+
"scripts": {
|
|
64
|
+
"sync": "node scripts/sync-lib.js",
|
|
65
|
+
"prepack": "npm run sync",
|
|
66
|
+
"test": "node --test test/*.test.js",
|
|
67
|
+
"test:coverage": "c8 node --test test/*.test.js",
|
|
68
|
+
"e2e": "node scripts/e2e-dsh.js",
|
|
69
|
+
"stress": "node scripts/stress-dsh.js"
|
|
70
|
+
},
|
|
71
|
+
"dependencies": {
|
|
72
|
+
"@huggingface/transformers": "^4.2.0"
|
|
73
|
+
},
|
|
74
|
+
"overrides": {
|
|
75
|
+
"adm-zip": "0.6.0"
|
|
76
|
+
},
|
|
77
|
+
"c8": {
|
|
78
|
+
"reporter": ["text", "lcov"]
|
|
57
79
|
}
|
|
58
80
|
}
|
|
@@ -17,6 +17,11 @@ export const Config = z.object({
|
|
|
17
17
|
summarizeModel: z.string().default(""),
|
|
18
18
|
maxInjectedItems: z.natural().min(1).max(20).default(5),
|
|
19
19
|
importanceThreshold: z.natural().min(1).max(5).default(3),
|
|
20
|
+
// 时间前缀注入(issue #34,opt-in,默认关):开启时,新对话开始时把当前
|
|
21
|
+
// 日期时间以 [当前时间: 2026-08-26 周二 19:30] 前缀注入一次,让模型感知
|
|
22
|
+
// "现在几点/周几"。只注入一次(对话开始时),同会话后续渲染不再重复;
|
|
23
|
+
// 关闭时行为与之前完全一致。
|
|
24
|
+
injectTimePrefix: z.boolean().default(false),
|
|
20
25
|
autoDream: z.boolean().default(true),
|
|
21
26
|
dreamThresholdCount: z.natural().min(1).max(1000).default(10),
|
|
22
27
|
dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
|
|
@@ -75,6 +75,32 @@ export function createInjector(ctx, service, settings, config) {
|
|
|
75
75
|
const maxItems = config.maxInjectedItems ?? 5;
|
|
76
76
|
const threshold = config.importanceThreshold ?? 3;
|
|
77
77
|
|
|
78
|
+
// Time-prefix injection (issue #34): opt-in, off by default. When enabled the
|
|
79
|
+
// current date/time is injected once per conversation — at the first prompt
|
|
80
|
+
// assembly of a new session — so the model can sense what time/day it is.
|
|
81
|
+
// A per-session latch means later assemblies in the same session never
|
|
82
|
+
// re-inject; a bare render ctx (no session id) falls back to once per
|
|
83
|
+
// injector lifetime.
|
|
84
|
+
const WEEKDAYS = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"];
|
|
85
|
+
let timePrefixSession = null;
|
|
86
|
+
|
|
87
|
+
function renderTimePrefix(ctx) {
|
|
88
|
+
if (config.injectTimePrefix !== true) return "";
|
|
89
|
+
const sessionId = ctx?.agent?.session?.id;
|
|
90
|
+
if (sessionId === undefined) {
|
|
91
|
+
if (timePrefixSession !== null) return "";
|
|
92
|
+
timePrefixSession = true;
|
|
93
|
+
} else {
|
|
94
|
+
if (sessionId === timePrefixSession) return "";
|
|
95
|
+
timePrefixSession = sessionId;
|
|
96
|
+
}
|
|
97
|
+
const now = new Date();
|
|
98
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
99
|
+
const date = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
|
100
|
+
const time = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
|
101
|
+
return `[当前时间: ${date} ${WEEKDAYS[now.getDay()]} ${time}]`;
|
|
102
|
+
}
|
|
103
|
+
|
|
78
104
|
// Bug6: bound the injected memory block. Each entry's content is truncated to
|
|
79
105
|
// MAX_CONTENT chars (trailing `…`); the whole block gets a MAX_BLOCK budget
|
|
80
106
|
// and an entry that would exceed it collapses to its title only, so a long
|
|
@@ -188,8 +214,11 @@ export function createInjector(ctx, service, settings, config) {
|
|
|
188
214
|
// separate context) keeps the prompt assembly stable at two blocks.
|
|
189
215
|
const hotText = renderHotContext(ctx);
|
|
190
216
|
const body = render(candidates);
|
|
191
|
-
|
|
192
|
-
|
|
217
|
+
let out = !hotText ? body : (body ? `${hotText}\n\n${body}` : hotText);
|
|
218
|
+
// Time prefix (issue #34): leads the injection block once per session.
|
|
219
|
+
const timePrefix = renderTimePrefix(ctx);
|
|
220
|
+
if (!timePrefix) return out;
|
|
221
|
+
return out ? `${timePrefix}\n\n${out}` : timePrefix;
|
|
193
222
|
}
|
|
194
223
|
}),
|
|
195
224
|
ctx.systemPrompt.context({
|
|
@@ -366,3 +366,40 @@ test("directory ships localized labels in both dictionaries", () => {
|
|
|
366
366
|
);
|
|
367
367
|
}
|
|
368
368
|
});
|
|
369
|
+
|
|
370
|
+
// issue #35: the delete confirm step must not depend on the host window.confirm —
|
|
371
|
+
// in the DSH web host the native dialog is unreliable/blocked, so clicks
|
|
372
|
+
// appeared to do nothing. Deletion is now an inline two-step confirm
|
|
373
|
+
// (✕ → 确认? → DELETE), gated per row by confirmingId.
|
|
374
|
+
test("delete flow drops window.confirm for an inline two-step confirm", () => {
|
|
375
|
+
assert.equal(
|
|
376
|
+
/window\.confirm\(/.test(clientSource),
|
|
377
|
+
false,
|
|
378
|
+
"the delete path must not call the host window.confirm"
|
|
379
|
+
);
|
|
380
|
+
assert.ok(
|
|
381
|
+
clientSource.includes("confirmingId"),
|
|
382
|
+
"a per-row confirm state must gate the second click"
|
|
383
|
+
);
|
|
384
|
+
assert.ok(
|
|
385
|
+
clientSource.includes('"directory.deleteConfirmShort"'),
|
|
386
|
+
"the short inline confirm label must be localized"
|
|
387
|
+
);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// issue #35: a failed DELETE (network / 401 / 500 / deleted!==true) must be
|
|
391
|
+
// visible — the old code swallowed it with a bare return and .catch(() => {}).
|
|
392
|
+
test("delete failure surfaces visible error feedback", () => {
|
|
393
|
+
assert.ok(
|
|
394
|
+
clientSource.includes("delErrorId"),
|
|
395
|
+
"a per-row error state must track failed deletes"
|
|
396
|
+
);
|
|
397
|
+
assert.ok(
|
|
398
|
+
clientSource.includes('"directory.deleteError"'),
|
|
399
|
+
"the delete error message must be localized"
|
|
400
|
+
);
|
|
401
|
+
assert.ok(
|
|
402
|
+
clientSource.includes(".mneme-dir-error"),
|
|
403
|
+
"a failed row must get a visible error style"
|
|
404
|
+
);
|
|
405
|
+
});
|
|
@@ -27,6 +27,13 @@ test("dream sliding window + implicit keep config defaults and bounds (v0.4.4)",
|
|
|
27
27
|
assert.equal(off.dreamImplicitKeep, false, "implicit keep can be disabled");
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
+
test("injectTimePrefix is opt-in: default off, enabled explicitly (issue #34)", () => {
|
|
31
|
+
const cfg = Config({});
|
|
32
|
+
assert.equal(cfg.injectTimePrefix, false, "time prefix defaults to false (behavior unchanged)");
|
|
33
|
+
const on = Config({ injectTimePrefix: true });
|
|
34
|
+
assert.equal(on.injectTimePrefix, true, "explicit opt-in enables it");
|
|
35
|
+
});
|
|
36
|
+
|
|
30
37
|
test("dream explicit coverage threshold config defaults and bounds (v0.4.4 fix)", () => {
|
|
31
38
|
const cfg = Config({});
|
|
32
39
|
assert.equal(cfg.dreamMinExplicitCoverage, 0.5, "coverage threshold defaults to 0.5");
|
|
@@ -101,3 +101,31 @@ test("Bug6: injected block stays within the ~1500 char budget, later entries col
|
|
|
101
101
|
// The first entries render full bodies; every entry is present by title.
|
|
102
102
|
for (let i = 0; i < 8; i++) assert.ok(text.includes(`长标题记忆${i}`), `entry ${i} present`);
|
|
103
103
|
});
|
|
104
|
+
|
|
105
|
+
test("injectTimePrefix: off by default, no time prefix injected", () => {
|
|
106
|
+
const { contexts } = setup();
|
|
107
|
+
const text = contexts[0].text({ agent: { session: { id: "s1" } } });
|
|
108
|
+
assert.ok(!text.includes("[当前时间:"), "no time prefix when disabled");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("injectTimePrefix: enabled injects once at conversation start with correct format", () => {
|
|
112
|
+
const { contexts, service } = setup({ injectTimePrefix: true });
|
|
113
|
+
service.saveWithDedupe({ type: "preference", title: "语言", content: "用户用中文交流", importance: 5 });
|
|
114
|
+
const text = contexts[0].text({ agent: { session: { id: "s1" } } });
|
|
115
|
+
assert.match(
|
|
116
|
+
text,
|
|
117
|
+
/^\[当前时间: \d{4}-\d{2}-\d{2} 周[一二三四五六日] \d{2}:\d{2}\]\n\n/,
|
|
118
|
+
"prefix leads the injection with the documented format"
|
|
119
|
+
);
|
|
120
|
+
assert.ok(text.includes("语言"), "memory body still rendered after the prefix");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("injectTimePrefix: same session never re-injects, a new session injects again", () => {
|
|
124
|
+
const { contexts } = setup({ injectTimePrefix: true });
|
|
125
|
+
const first = contexts[0].text({ agent: { session: { id: "s1" } } });
|
|
126
|
+
assert.match(first, /^\[当前时间:/, "injects on the first render of s1");
|
|
127
|
+
const second = contexts[0].text({ agent: { session: { id: "s1" } } });
|
|
128
|
+
assert.ok(!second.includes("[当前时间:"), "no re-inject within the same session");
|
|
129
|
+
const third = contexts[0].text({ agent: { session: { id: "s2" } } });
|
|
130
|
+
assert.match(third, /^\[当前时间:/, "a new session injects the prefix again");
|
|
131
|
+
});
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Test
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master, main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master, main]
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
test:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
strategy:
|
|
14
|
-
matrix:
|
|
15
|
-
node-version: [24]
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: ${{ matrix.node-version }}
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
working-directory: dsh-mneme
|
|
23
|
-
run: npm ci
|
|
24
|
-
- name: Run tests
|
|
25
|
-
working-directory: dsh-mneme
|
|
26
|
-
run: npm run test:coverage
|
|
27
|
-
- name: Upload coverage to Codecov
|
|
28
|
-
uses: codecov/codecov-action@v4
|
|
29
|
-
with:
|
|
30
|
-
token: ${{ secrets.CODECOV_TOKEN }}
|
|
31
|
-
files: dsh-mneme/coverage/lcov.info
|
|
32
|
-
fail_ci_if_error: false
|
package/CHANGELOG.md
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to dsh-mneme are documented here.
|
|
4
|
-
|
|
5
|
-
## [Unreleased]
|
|
6
|
-
|
|
7
|
-
- **诚实审计修复(v0.2.7,F-03)**
|
|
8
|
-
- autoDream 决策无变更且 summary 为空时,不再误报 ok:true——新增 `noop` 状态(ok:false,baseline 不刷新),避免空跑循环
|
|
9
|
-
- 有变更但 summary 缺失 → `degraded`(ok:true 但如实标记 summary 缺失)
|
|
10
|
-
- parseReceipt 支持 ok/noop/degraded/reconcile/failed
|
|
11
|
-
- 测试 259 → **263**
|
|
12
|
-
|
|
13
|
-
- **社区贡献 · 嵌入式面板 UI 改进(PR #2,@Liuxin4950)**
|
|
14
|
-
- 记忆面板与设置面板的嵌入模式(settings.section 插槽)不再复用弹窗面板样式(去掉 boxShadow / background / borderRadius / padding),改为全宽平铺渲染,消除 DSH 设置页内多余的悬浮卡片与阴影
|
|
15
|
-
- 弹窗(portal)模式保持原有面板样式不变;新增回归测试锁定 embedded 分支不携带 modal chrome
|
|
16
|
-
|
|
17
|
-
- **安全审计修复(v0.2.5)**
|
|
18
|
-
- 并发安全:CAS 冲突守卫——过期快照不再覆盖并发写入(防丢更新)
|
|
19
|
-
- 事务化决策应用:merge/archive 原子提交,receipt 反映已提交子步骤,部分提交 = reconcile(绝不虚报 ok)
|
|
20
|
-
- 压测硬断言:lost-update 与多步原子性失败即非零退出(`npm run stress`)
|
|
21
|
-
- 运行时人工编辑三方合并(three-way),不再静默覆盖
|
|
22
|
-
- 新增 `memory_archive` 工具(第 7 个模型工具)+ `memory_list include_archived`,归档可恢复
|
|
23
|
-
- reranker 改为 opt-in:`rerankEnabled=false`、`rerankProvider=none` 默认(裸装不加载 onnxruntime)
|
|
24
|
-
- npm `files` 纳入 scripts/test,tarball 内可直接跑压测
|
|
25
|
-
- 测试 236 → **258**(+22)
|
|
26
|
-
|
|
27
|
-
- **安全加固(v0.2.4)**:API 鉴权 `apiToken`(写操作与密钥端点要求 Bearer 校验)+ `apiKey` 掩码回传 + timing-safe token 比对
|
|
28
|
-
|
|
29
|
-
- **审查修复补丁(v0.2.3)**
|
|
30
|
-
- failure 记录增加 `before` JSON 快照(title/content/importance 变更可追溯,不只 content)
|
|
31
|
-
- vector-index `rebuildIndex` guard 改为检查 `embedSingle`(修复 embed/embedSingle 不一致导致的静默跳过)
|
|
32
|
-
|
|
33
|
-
- **流水线补全(v0.2.2)**
|
|
34
|
-
- reflection 修复:failure 记录检查 title/importance 变化(不只 content);支持 query 上下文(memory_update 加 `reason` 参数);update 校验失败不污染 claimed;`failure_memories` 清理(`deleteOldFailures` + 启动自动清理 90 天前)
|
|
35
|
-
- 专项测试补全:`vector-index.test.js`(modelHash 漂移/重建/增量)+ `service-search.test.js`(hybrid/auto/rerank 端到端)
|
|
36
|
-
- api.js `/search` 的 mode 参数文档化
|
|
37
|
-
- 测试 212 → **233**
|
|
38
|
-
|
|
39
|
-
- **反思更新(v0.2.1)**:`update` 决策 + 失败追踪
|
|
40
|
-
- autoDream 新增 `update` 决策类型:修正单条记忆的过时/错误内容(单 id、必须实际变化、非 summary、24h 保护、每次 ≤2)
|
|
41
|
-
- 审计记录 update 的 `_before` 快照;update 后向量索引同步
|
|
42
|
-
- `failure_memories` 表:记录用户纠正(user_correction)+ 查询/统计接口
|
|
43
|
-
- 配置:`reflectionUpdateEnabled` / `reflectionFailureTracking` / `reflectionUpdateMaxPerRun` / `reflectionUpdateMinAgeHours`
|
|
44
|
-
|
|
45
|
-
- **Semantic 升级(v0.2.0-semantic)**:完全离线语义记忆引擎
|
|
46
|
-
- 本地 Embedding:`embedProvider: local`(ONNX bge-small-zh-v1.5,transformers.js/onnxruntime)或 `ollama`;原 `openai` 外部 API 保留为默认,向后兼容
|
|
47
|
-
- 向量索引层:`vector_meta` 模型指纹追踪 + 索引统计(`/api/dsh-mneme/semantic`)
|
|
48
|
-
- Rerank 精排:`rerankEnabled`(bge-reranker-base cross-encoder),召回后精排 Top-K,失败自动跳过
|
|
49
|
-
- 混合搜索:`memory_search` 新增 `mode: hybrid`(向量优先 + 关键词补位);`auto` 保持关键词优先 + 向量补位
|
|
50
|
-
- autoDream 语义增强:K-Means 聚类预分组(k-means++)、`[潜在冲突]` 向量相似度标记、整理后向量索引重建
|
|
51
|
-
- 模型下载:断点续传 + 缓存(transformers.js 内置);`.npmrc` 跳过 onnxruntime CUDA 下载避免安装失败
|
|
52
|
-
- 文档:`docs/SEMANTIC.md` / `docs/LOCAL_MODEL.md` / `docs/MIGRATION.md` + 基准脚本 `benchmark-embed` / `benchmark-rerank`
|
|
53
|
-
- autoDream 裁决审计:每次运行写入 `dream_runs`(输入快照 sha256 digest + 完整输入快照 + LLM 决策清单 + 逐 id 去向 + receipt `dsh-mneme:run:<id>:<status>:<hash>:<count>:<applied>`),可离线回放、定位静默错误
|
|
54
|
-
- 幂等决策应用:merge / conflict 重复应用无累积副作用(来源注释不重复追加),防并发/重放下的重复合并
|
|
55
|
-
- 三轴线压测 `npm run stress`:长会话检索(Recall@k、陈旧残留率)/ 冲突裁决(可重放仲裁集)/ 多 Agent 并发(丢更新、重复合并、事务/崩溃恢复)
|
|
56
|
-
|
|
57
|
-
## [0.1.6] - 2026-08-14
|
|
58
|
-
|
|
59
|
-
- Vector (semantic) search via OpenAI-compatible embeddings endpoint, with automatic fallback to LIKE keyword search on failure
|
|
60
|
-
- Web GUI memory panel: browse by type, full-text + semantic search
|
|
61
|
-
|
|
62
|
-
## [0.1.5] - 2026-08-14
|
|
63
|
-
|
|
64
|
-
- autoDream consolidation refinements: conflict resolution with source tracking, fail-safe decision validation
|
|
65
|
-
|
|
66
|
-
## [0.1.4] - 2026-08-14
|
|
67
|
-
|
|
68
|
-
- User settings: profile + behavior rules injected every turn
|
|
69
|
-
- Custom slash commands (register, route to agent)
|
|
70
|
-
|
|
71
|
-
## [0.1.3] - 2026-08-14
|
|
72
|
-
|
|
73
|
-
- autoDream background consolidation: dedup / merge / archive / conflict resolution
|
|
74
|
-
|
|
75
|
-
## [0.1.2] - 2026-08-14
|
|
76
|
-
|
|
77
|
-
- Session summarization: auto-distill preferences / decisions / lessons at session end
|
|
78
|
-
- Web panel improvements
|
|
79
|
-
|
|
80
|
-
## [0.1.1] - 2026-08-13
|
|
81
|
-
|
|
82
|
-
- Memory tools: memory_save / memory_search / memory_list / memory_update / memory_delete / memory_forget
|
|
83
|
-
- Markdown mirror sync (human-editable, manual edits take priority)
|
|
84
|
-
|
|
85
|
-
## [0.1.0] - 2026-08-13
|
|
86
|
-
|
|
87
|
-
- Initial release: cross-session memory for DeepSeek Harness
|
|
88
|
-
- SQLite store (node:sqlite, zero native deps) + human-editable Markdown mirror
|
|
89
|
-
- Auto-injection of relevant memories at session start
|