@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.
Files changed (145) hide show
  1. package/README.md +415 -152
  2. package/{dsh-mneme/lib → lib}/client.js +55 -8
  3. package/{dsh-mneme/src → lib}/config.js +5 -0
  4. package/{dsh-mneme/lib → lib}/inject.js +31 -2
  5. package/package.json +40 -18
  6. package/{dsh-mneme/lib → src}/config.js +5 -0
  7. package/{dsh-mneme/src → src}/inject.js +31 -2
  8. package/{dsh-mneme/test → test}/client.test.js +37 -0
  9. package/{dsh-mneme/test → test}/config.test.js +7 -0
  10. package/{dsh-mneme/test → test}/inject.test.js +28 -0
  11. package/.github/workflows/test.yml +0 -32
  12. package/CHANGELOG.md +0 -89
  13. package/CONTRIBUTING.md +0 -245
  14. package/SECURITY.md +0 -544
  15. package/docs/devlog/2026-08-14-dsh-mneme-dev-log.md +0 -247
  16. package/docs/devlog/2026-08-15-dsh-mneme-audit-stress-dev-log.md +0 -145
  17. package/docs/devlog/2026-08-15-dsh-mneme-pipeline-dev-log.md +0 -56
  18. package/docs/devlog/2026-08-15-dsh-mneme-reflection-dev-log.md +0 -77
  19. package/docs/devlog/2026-08-15-dsh-mneme-review-fixes-dev-log.md +0 -64
  20. package/docs/devlog/2026-08-15-dsh-mneme-semantic-dev-log.md +0 -90
  21. package/dsh-mneme/CHANGELOG.md +0 -295
  22. package/dsh-mneme/LICENSE +0 -21
  23. package/dsh-mneme/README.md +0 -484
  24. package/dsh-mneme/docs/AGENT_MEMORY_RESEARCH.md +0 -183
  25. package/dsh-mneme/docs/ENTITIES.md +0 -245
  26. package/dsh-mneme/docs/LOCAL_MODEL.md +0 -141
  27. package/dsh-mneme/docs/MIGRATION.md +0 -127
  28. package/dsh-mneme/docs/SEMANTIC.md +0 -256
  29. package/dsh-mneme/docs/SLEEP.md +0 -163
  30. package/dsh-mneme/package-lock.json +0 -1936
  31. package/dsh-mneme/package.json +0 -80
  32. package/v0.7-plan-2026-08-21.md +0 -75
  33. package//346/250/252/345/271/205.png +0 -0
  34. /package/{dsh-mneme/cordis.patch.yml → cordis.patch.yml} +0 -0
  35. /package/{dsh-mneme/lib → lib}/api.js +0 -0
  36. /package/{dsh-mneme/lib → lib}/commands.js +0 -0
  37. /package/{dsh-mneme/lib → lib}/dream/clustering.js +0 -0
  38. /package/{dsh-mneme/lib → lib}/dream/decisions.js +0 -0
  39. /package/{dsh-mneme/lib → lib}/dream/sleep.js +0 -0
  40. /package/{dsh-mneme/lib → lib}/dream/tag-extractor.js +0 -0
  41. /package/{dsh-mneme/lib → lib}/dream.js +0 -0
  42. /package/{dsh-mneme/lib → lib}/embedding.js +0 -0
  43. /package/{dsh-mneme/lib → lib}/entities/extractor.js +0 -0
  44. /package/{dsh-mneme/lib → lib}/heat.js +0 -0
  45. /package/{dsh-mneme/lib → lib}/hot-memory.js +0 -0
  46. /package/{dsh-mneme/lib → lib}/index.js +0 -0
  47. /package/{dsh-mneme/lib → lib}/local-embedder.js +0 -0
  48. /package/{dsh-mneme/lib → lib}/mirror.js +0 -0
  49. /package/{dsh-mneme/lib → lib}/parser/tag.js +0 -0
  50. /package/{dsh-mneme/lib → lib}/parser/wiki-link.js +0 -0
  51. /package/{dsh-mneme/lib → lib}/quality-filter.js +0 -0
  52. /package/{dsh-mneme/lib → lib}/reranker.js +0 -0
  53. /package/{dsh-mneme/lib → lib}/search/adaptive.js +0 -0
  54. /package/{dsh-mneme/lib → lib}/search/bm25.js +0 -0
  55. /package/{dsh-mneme/lib → lib}/search/tag-boost.js +0 -0
  56. /package/{dsh-mneme/lib → lib}/service.js +0 -0
  57. /package/{dsh-mneme/lib → lib}/settings.js +0 -0
  58. /package/{dsh-mneme/lib → lib}/store.js +0 -0
  59. /package/{dsh-mneme/lib → lib}/summarize.js +0 -0
  60. /package/{dsh-mneme/lib → lib}/tools.js +0 -0
  61. /package/{dsh-mneme/lib → lib}/vector-index.js +0 -0
  62. /package/{dsh-mneme/scripts → scripts}/benchmark-embed.js +0 -0
  63. /package/{dsh-mneme/scripts → scripts}/benchmark-recall.js +0 -0
  64. /package/{dsh-mneme/scripts → scripts}/benchmark-rerank.js +0 -0
  65. /package/{dsh-mneme/scripts → scripts}/e2e-dsh.js +0 -0
  66. /package/{dsh-mneme/scripts → scripts}/stress-dsh.js +0 -0
  67. /package/{dsh-mneme/scripts → scripts}/sync-lib.js +0 -0
  68. /package/{dsh-mneme/src → src}/api.js +0 -0
  69. /package/{dsh-mneme/src → src}/commands.js +0 -0
  70. /package/{dsh-mneme/src → src}/dream/clustering.js +0 -0
  71. /package/{dsh-mneme/src → src}/dream/decisions.js +0 -0
  72. /package/{dsh-mneme/src → src}/dream/sleep.js +0 -0
  73. /package/{dsh-mneme/src → src}/dream/tag-extractor.js +0 -0
  74. /package/{dsh-mneme/src → src}/dream.js +0 -0
  75. /package/{dsh-mneme/src → src}/embedding.js +0 -0
  76. /package/{dsh-mneme/src → src}/entities/extractor.js +0 -0
  77. /package/{dsh-mneme/src → src}/heat.js +0 -0
  78. /package/{dsh-mneme/src → src}/hot-memory.js +0 -0
  79. /package/{dsh-mneme/src → src}/index.js +0 -0
  80. /package/{dsh-mneme/src → src}/local-embedder.js +0 -0
  81. /package/{dsh-mneme/src → src}/mirror.js +0 -0
  82. /package/{dsh-mneme/src → src}/parser/tag.js +0 -0
  83. /package/{dsh-mneme/src → src}/parser/wiki-link.js +0 -0
  84. /package/{dsh-mneme/src → src}/quality-filter.js +0 -0
  85. /package/{dsh-mneme/src → src}/reranker.js +0 -0
  86. /package/{dsh-mneme/src → src}/search/adaptive.js +0 -0
  87. /package/{dsh-mneme/src → src}/search/bm25.js +0 -0
  88. /package/{dsh-mneme/src → src}/search/tag-boost.js +0 -0
  89. /package/{dsh-mneme/src → src}/service.js +0 -0
  90. /package/{dsh-mneme/src → src}/settings.js +0 -0
  91. /package/{dsh-mneme/src → src}/store.js +0 -0
  92. /package/{dsh-mneme/src → src}/summarize.js +0 -0
  93. /package/{dsh-mneme/src → src}/tools.js +0 -0
  94. /package/{dsh-mneme/src → src}/vector-index.js +0 -0
  95. /package/{dsh-mneme/test → test}/api.test.js +0 -0
  96. /package/{dsh-mneme/test → test}/audit.test.js +0 -0
  97. /package/{dsh-mneme/test → test}/benchmark.test.js +0 -0
  98. /package/{dsh-mneme/test → test}/boundary-v0625.test.js +0 -0
  99. /package/{dsh-mneme/test → test}/clustering.test.js +0 -0
  100. /package/{dsh-mneme/test → test}/commands.test.js +0 -0
  101. /package/{dsh-mneme/test → test}/conflict-freeze.test.js +0 -0
  102. /package/{dsh-mneme/test → test}/directory.test.js +0 -0
  103. /package/{dsh-mneme/test → test}/dream.test.js +0 -0
  104. /package/{dsh-mneme/test → test}/entities.test.js +0 -0
  105. /package/{dsh-mneme/test → test}/epistemic.test.js +0 -0
  106. /package/{dsh-mneme/test → test}/fnew-0112.test.js +0 -0
  107. /package/{dsh-mneme/test → test}/fnew-03.test.js +0 -0
  108. /package/{dsh-mneme/test → test}/graph-api.test.js +0 -0
  109. /package/{dsh-mneme/test → test}/heat.test.js +0 -0
  110. /package/{dsh-mneme/test → test}/helpers/dream-mock.js +0 -0
  111. /package/{dsh-mneme/test → test}/hot-memory.test.js +0 -0
  112. /package/{dsh-mneme/test → test}/llm-audit.test.js +0 -0
  113. /package/{dsh-mneme/test → test}/local-embedder.test.js +0 -0
  114. /package/{dsh-mneme/test → test}/mirror-dirty.test.js +0 -0
  115. /package/{dsh-mneme/test → test}/mirror-edit-digest.test.js +0 -0
  116. /package/{dsh-mneme/test → test}/mirror-generation.test.js +0 -0
  117. /package/{dsh-mneme/test → test}/mirror.test.js +0 -0
  118. /package/{dsh-mneme/test → test}/normalize-decisions.test.js +0 -0
  119. /package/{dsh-mneme/test → test}/peer-blockers.test.js +0 -0
  120. /package/{dsh-mneme/test → test}/policy-epoch.test.js +0 -0
  121. /package/{dsh-mneme/test → test}/provenance.test.js +0 -0
  122. /package/{dsh-mneme/test → test}/quality-filter.test.js +0 -0
  123. /package/{dsh-mneme/test → test}/reasoning-effort.test.js +0 -0
  124. /package/{dsh-mneme/test → test}/recall-evals.test.js +0 -0
  125. /package/{dsh-mneme/test → test}/recall-layer.test.js +0 -0
  126. /package/{dsh-mneme/test → test}/recall-runs.test.js +0 -0
  127. /package/{dsh-mneme/test → test}/receipt-chain.test.js +0 -0
  128. /package/{dsh-mneme/test → test}/reflection.test.js +0 -0
  129. /package/{dsh-mneme/test → test}/reranker.test.js +0 -0
  130. /package/{dsh-mneme/test → test}/search-fusion.test.js +0 -0
  131. /package/{dsh-mneme/test → test}/semantic.test.js +0 -0
  132. /package/{dsh-mneme/test → test}/service-search.test.js +0 -0
  133. /package/{dsh-mneme/test → test}/service.test.js +0 -0
  134. /package/{dsh-mneme/test → test}/settings.test.js +0 -0
  135. /package/{dsh-mneme/test → test}/sleep-heat.test.js +0 -0
  136. /package/{dsh-mneme/test → test}/sleep.test.js +0 -0
  137. /package/{dsh-mneme/test → test}/store.test.js +0 -0
  138. /package/{dsh-mneme/test → test}/stress.test.js +0 -0
  139. /package/{dsh-mneme/test → test}/summarize.test.js +0 -0
  140. /package/{dsh-mneme/test → test}/tag-boost.test.js +0 -0
  141. /package/{dsh-mneme/test → test}/tag.test.js +0 -0
  142. /package/{dsh-mneme/test → test}/tools.test.js +0 -0
  143. /package/{dsh-mneme/test → test}/updated-at-semantics.test.js +0 -0
  144. /package/{dsh-mneme/test → test}/vector-index.test.js +0 -0
  145. /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
- if (typeof window === "undefined" || !window.confirm(t("directory.deleteConfirm"))) return;
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) return;
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
- h("div", { key: m.id, className: "mneme-dir-item" },
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", { type: "button", className: "mneme-dir-del", "aria-label": "delete", onClick: (e) => handleDelete(m.id, e) }, "✕")
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
- if (!hotText) return body;
192
- return body ? `${hotText}\n\n${body}` : hotText;
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
- "version": "0.7.1",
4
- "description": "Structured memory engine for DeepSeek Harness. Offline semantic search, entity-attribute-timeline, autoDream self-consolidation, and human-editable Markdown storage.",
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": "./dsh-mneme/lib/index.js",
15
+ "main": "lib/index.js",
16
16
  "exports": {
17
17
  ".": {
18
- "default": "./dsh-mneme/lib/index.js"
18
+ "default": "./lib/index.js"
19
19
  },
20
20
  "./client": {
21
- "default": "./dsh-mneme/lib/client.js"
21
+ "default": "./lib/client.js"
22
22
  },
23
- "./package.json": "./dsh-mneme/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": "./dsh-mneme/cordis.patch.yml"
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
- if (!hotText) return body;
192
- return body ? `${hotText}\n\n${body}` : hotText;
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