@a9i5k4/dsh-auto-memory 3.0.1 → 3.1.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.
Files changed (124) hide show
  1. package/README.md +13 -8
  2. package/README.zh-CN.md +13 -8
  3. package/docs/HANDBOOK.md +88 -52
  4. package/docs/USER-GUIDE.en.md +9 -9
  5. package/docs/USER-GUIDE.zh-CN.md +9 -9
  6. package/docs/screenshots/promo/promo-0-banner-v4.png +0 -0
  7. package/docs/screenshots/promo/promo-1b-auto-recall.png +0 -0
  8. package/lib/activation-host.js +6 -1
  9. package/lib/client.js +819 -272
  10. package/lib/context-host.js +7 -1
  11. package/lib/episodic-store.js +90 -16
  12. package/lib/fact-store.js +463 -41
  13. package/lib/hub-io.js +217 -0
  14. package/lib/index.js +224 -45
  15. package/lib/intent-clean-safe.js +1 -1
  16. package/lib/memory-hub.js +37 -5
  17. package/lib/note-status.js +9 -1
  18. package/lib/procedure-store.js +252 -31
  19. package/lib/procedure-switch.js +38 -0
  20. package/lib/python-sidecar-client.js +285 -8
  21. package/package.json +6 -2
  22. package/docs/internal/ACCEPT-35-LIVE.md +0 -143
  23. package/docs/internal/ACCEPTANCE-20260914.md +0 -90
  24. package/docs/internal/ARCH-REVIEW-BRIEF.md +0 -411
  25. package/docs/internal/ARCH-REVIEW-REQUEST.md +0 -201
  26. package/docs/internal/ARCH-REVIEW-ROUND2.md +0 -169
  27. package/docs/internal/ARCH-REVIEW-ROUND3.md +0 -206
  28. package/docs/internal/ARCHITECTURE-FOR-ZCODE-20260920.md +0 -397
  29. package/docs/internal/ART-DIRECTION-DEEPSEEK-20260920.md +0 -351
  30. package/docs/internal/ART-DIRECTION-WIREFRAME.md +0 -191
  31. package/docs/internal/ART-DIRECTION-WIREFRAME.md.bak-superseded +0 -181
  32. package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +0 -314
  33. package/docs/internal/BATTLE-PLAN-20260917.md +0 -871
  34. package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +0 -192
  35. package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +0 -72
  36. package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +0 -131
  37. package/docs/internal/CUA-VISION-FIX-NOTES.md +0 -78
  38. package/docs/internal/DECISIONS-20260914-SESSION.md +0 -269
  39. package/docs/internal/DESIGN-OVERHAUL-PRE-RESEARCH.md +0 -292
  40. package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +0 -219
  41. package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +0 -132
  42. package/docs/internal/FEATURE-INVENTORY.md +0 -531
  43. package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +0 -13
  44. package/docs/internal/G-SERIES-EXECUTION-20260917.md +0 -248
  45. package/docs/internal/G3-DESIGN-20260918.md +0 -82
  46. package/docs/internal/G3-DISK-FORMAT-GAP-20260919.md +0 -92
  47. package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +0 -74
  48. package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +0 -352
  49. package/docs/internal/GPT-REVIEW-PROMPT.md +0 -216
  50. package/docs/internal/GROUP-DIGEST-SETUP.md +0 -62
  51. package/docs/internal/GROUP-LISTENER-SETUP.md +0 -49
  52. package/docs/internal/GROUP-WEBHOOK-SETUP.md +0 -93
  53. package/docs/internal/HANDOFF-TO-ZCODE-20260920.md +0 -309
  54. package/docs/internal/HANDOFF-TO-ZCODE.md +0 -168
  55. package/docs/internal/HERMES-DATA-VERIFICATION-20260919.md +0 -120
  56. package/docs/internal/HERMES-LEGACY-STATUS-20260919.md +0 -74
  57. package/docs/internal/ISSUE-55-58-VERIFICATION-20260918.md +0 -175
  58. package/docs/internal/ISSUE10-FIX-EXECUTION-20260919.md +0 -389
  59. package/docs/internal/ISSUE10-PLAN-20260919.md +0 -254
  60. package/docs/internal/ISSUE10B-FORENSICS-20260919.md +0 -468
  61. package/docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md +0 -150
  62. package/docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md +0 -114
  63. package/docs/internal/KICKOFF-P0.md +0 -254
  64. package/docs/internal/LESSON-TO-CANDIDATE-STATUS-20260919.md +0 -79
  65. package/docs/internal/MASTER-PLAN-3.0.md +0 -411
  66. package/docs/internal/MEMORY-GOVERNANCE-20260917.md +0 -309
  67. package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +0 -85
  68. package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +0 -222
  69. package/docs/internal/NEXT-VERSION-TODO.md +0 -95
  70. package/docs/internal/OFFICIAL-DISCUSSION-DRAFT.md +0 -80
  71. package/docs/internal/PENDING-FIXES-20260916.md +0 -289
  72. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md +0 -705
  73. package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md.bak-s10 +0 -649
  74. package/docs/internal/PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md +0 -225
  75. package/docs/internal/PROGRESS-20260917.md +0 -93
  76. package/docs/internal/PROMPT-GAP-AUDIT-20260920.md +0 -128
  77. package/docs/internal/R1-DEGRADE-AUDIT-20260918.md +0 -163
  78. package/docs/internal/R1-READABILITY-FORENSICS-20260919.md +0 -127
  79. package/docs/internal/R2-EVIDENCE-DEEP-AUDIT-20260918.md +0 -140
  80. package/docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md +0 -138
  81. package/docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md +0 -218
  82. package/docs/internal/RAG-KARPATHY-PROGRAM.md +0 -229
  83. package/docs/internal/RELEASE-PROCESS.md +0 -99
  84. package/docs/internal/REPORT-P0-NIGHTLY.md +0 -212
  85. package/docs/internal/REPORT-P5-ACCEPTANCE.md +0 -31
  86. package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +0 -153
  87. package/docs/internal/RESUME-20260918.md +0 -171
  88. package/docs/internal/RESUME-20260919.md +0 -104
  89. package/docs/internal/REVIEW-WB-GRAPH-SELF.md +0 -81
  90. package/docs/internal/RHINELAB-TO-DEEPSEEK-FEASIBILITY.md +0 -198
  91. package/docs/internal/ROADMAP-20260917-WEEK.md +0 -439
  92. package/docs/internal/ROADMAP.md +0 -106
  93. package/docs/internal/RUN-P0-NIGHTLY.md +0 -227
  94. package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +0 -185
  95. package/docs/internal/S10-GAP-INVENTORY-20260917.md +0 -239
  96. package/docs/internal/S10-GAPS-PLAIN-20260917.md +0 -125
  97. package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +0 -360
  98. package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +0 -90
  99. package/docs/internal/SUBAGENT-REPORT-ROUTING-PRE-RESEARCH.md +0 -261
  100. package/docs/internal/T6-EXECUTION-20260920.md +0 -130
  101. package/docs/internal/TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md +0 -146
  102. package/docs/internal/THESIS-GAP-ANALYSIS-20260918.md +0 -89
  103. package/docs/internal/THESIS-OUTLINE-20260918.md +0 -147
  104. package/docs/internal/THREE-LAYER-CONTRACT.md +0 -219
  105. package/docs/internal/TODO-BACKLOG.md +0 -263
  106. package/docs/internal/TODO-GRAPH.html +0 -715
  107. package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +0 -493
  108. package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +0 -710
  109. package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +0 -710
  110. package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +0 -703
  111. package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +0 -710
  112. package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +0 -715
  113. package/docs/internal/UPSTREAM-ISSUE-PR-TRIAGE-20260919.md +0 -297
  114. package/docs/internal/UPSTREAM-ISSUES-3RD-AUDIT-20260920.md +0 -104
  115. package/docs/internal/WB-FORMAT-CONVENTION.md +0 -112
  116. package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +0 -71
  117. package/docs/internal/WB-GRAPH-INTEGRATION-PLAN.md +0 -386
  118. package/docs/internal/WB-GRAPH-RESEARCH-BRIEF.md +0 -118
  119. package/docs/internal/WB-GRAPH-RESEARCH-EXTERNAL.md +0 -228
  120. package/docs/internal/WB-GRAPH-RESEARCH-LOCAL.md +0 -190
  121. package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +0 -56
  122. package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +0 -787
  123. package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +0 -112
  124. package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +0 -230
@@ -28,6 +28,7 @@ import { createPythonContextSinkPre } from './context-sink-python.js'
28
28
  import { buildQueryPlan, lexicalSearch, GATE_POLICY_VERSION, LEXICAL_POLICY_VERSION } from './shadow-retrieval.js'
29
29
  import { fuseD6Pre } from './semantic-js.js'
30
30
  import { buildSourceCatalog, loadCorpusSnapshot, CorpusRegistry, canonicalize } from './m4-corpus.js'
31
+ import { resolveProcedureInjectEnabledPre } from './procedure-switch.js'
31
32
 
32
33
  const MAX_DROPS_RING = 64
33
34
  /** 同一 drop 原因的日志限流窗口(ms):窗口内只写首条,其余计数。防会话回放刷屏(2026-09-10)。 */
@@ -534,7 +535,12 @@ export function createContextHost(opts = {}) {
534
535
  // 集合不变则缓存命中);不可用回退词法 2-gram。阈值 0.6(e5 相关内容带)。单 offer 出口。
535
536
  try {
536
537
  const hub = engine._memoryHub
537
- const skillEnabled = engine.config.memoryHubEnabled === true && engine.config.procedurePromotionEnabled !== false
538
+ // B-2:旧键 procedurePromotionEnabled 是**语义错配的兼容别名**——它的键名与界面文案写的是
539
+ // 「技能固化与晋升」,但它实际控制的是「已激活的技能是否随本轮上下文注入」这道总闸,
540
+ // 与晋升流程本身无关。改由接口契约模块 lib/procedure-switch.js 统一解析:
541
+ // 新键 procedureInjectEnabled 优先,缺省回退旧键,两键都缺省按开启处理;
542
+ // 老用户显式关掉过总闸的,行为不翻面。此处只替换开关解析口径,不新增任何前置条件。
543
+ const skillEnabled = engine.config.memoryHubEnabled === true && resolveProcedureInjectEnabledPre(engine.config)
538
544
  if (hub && skillEnabled && hub.stores && hub.stores.procedures) {
539
545
  const actives = hub.stores.procedures.activeProcedures()
540
546
  let hit = null
@@ -121,15 +121,43 @@ export function createEpisodicStorePre(opts = {}) {
121
121
  return raw
122
122
  }
123
123
 
124
+ /**
125
+ * ★ P2-2 修复(2026-09-21):查询返回**深一层副本**。
126
+ * 旧实现 `{ ...e }` 是浅拷贝 ⇒ `actions/entities/unresolved/provenance` 四个数组
127
+ * 与库内对象**同引用**,调用方 push 会直接改脏已巩固 episode(绕过 persist 与统计)。
128
+ */
129
+ function cloneEpisode(e) {
130
+ if (!e) return e
131
+ return {
132
+ ...e,
133
+ actions: Array.isArray(e.actions) ? [...e.actions] : e.actions,
134
+ entities: Array.isArray(e.entities) ? [...e.entities] : e.entities,
135
+ unresolved: Array.isArray(e.unresolved) ? [...e.unresolved] : e.unresolved,
136
+ provenance: Array.isArray(e.provenance) ? [...e.provenance] : e.provenance,
137
+ }
138
+ }
139
+
124
140
  // ---- 段追加(会话进行中实时累积) ----
125
141
  function append(seg) {
126
142
  if (disposed) return { ok: false, reason: 'disposed' }
127
143
  const v = validateEpisodeSegmentPre(seg)
128
144
  if (!v.ok) return { ok: false, reason: v.reason }
129
145
  const s = v.segment
146
+ const incomingRef = String(s.sessionRef || 'unknown')
147
+ // ★ A-7 修复(2026-09-21):**按 sessionRef 隔离**。
148
+ // 旧实现只在 `!current` 时取一次 sessionRef ⇒ 后续不同会话的段被并进同一个 episode:
149
+ // 实测 sessionRef=session-A 但 intent 含 session-B 内容、provenance=["seg:1","seg:1"]。
150
+ // 后果:procedure 晋升依赖的 distinctSessions 被系统性低估 ⇒ 技能永远卡在
151
+ // `diversity-below-3`(跨会话证据明明够,读数却恒为 1)。
152
+ // 现改为:检测到会话切换时,**先巩固当前缓冲**(把上一会话的段固化成一个独立 episode),
153
+ // 再为新的 sessionRef 开一个干净的 current。consolidate 失败(如 too-short)不阻断本段,
154
+ // 其内部已负责把 current 置 null,后续照常新建。
155
+ if (current && current.sessionRef !== incomingRef) {
156
+ consolidate()
157
+ }
130
158
  if (!current) {
131
159
  current = {
132
- sessionRef: String(s.sessionRef || 'unknown'),
160
+ sessionRef: incomingRef,
133
161
  startedAt: nowFn(),
134
162
  segments: [],
135
163
  userTexts: [], assistantTexts: [],
@@ -156,7 +184,13 @@ export function createEpisodicStorePre(opts = {}) {
156
184
  if (current.assistantTexts.length) current.assistantTexts.shift()
157
185
  }
158
186
  stats.segmentsAppended++
159
- return { ok: true, segments: current.segments.length }
187
+ // ★ P2-11 修复(2026-09-21):append 此前**完全不落盘** —— 段只留在内存,
188
+ // 进程重启(崩溃/宿主重载)后未巩固的会话缓冲整段丢失,而调用方看到 ok:true。
189
+ // 现在按段落盘(与 fact-store 各写路径一致)。落盘结果一并透传(A-8 同款纪律)。
190
+ const pr = persist()
191
+ return pr.ok
192
+ ? { ok: true, segments: current.segments.length, persisted: true }
193
+ : { ok: true, segments: current.segments.length, persisted: false, persistError: pr.error }
160
194
  }
161
195
 
162
196
  /** 从累积文本提取 intent(2026-08-28 提纯;2026-09-16 issue#30 改为共享清洗器)。
@@ -232,13 +266,24 @@ export function createEpisodicStorePre(opts = {}) {
232
266
  unresolved: extractUnresolved(current),
233
267
  outcome,
234
268
  success: outcome === 'success',
235
- provenance: current.segments.map((s) => 'seg:' + String(s.eventSeq)),
269
+ // ★ A-7 修复(2026-09-21):provenance 改为 **`sessionRef:eventSeq`**。
270
+ // 旧实现只用 `'seg:' + eventSeq` ⇒ 不同会话里 eventSeq 从 1 重新计数时
271
+ // 会产出完全相同的串(实测 ["seg:1","seg:1"]),既无法溯源到会话,
272
+ // 也让"同一段被重复计入"与"两段恰好同号"在审计面上不可区分。
273
+ provenance: current.segments.map((s) => String(current.sessionRef) + ':' + String(s.eventSeq)),
236
274
  startedAt: current.startedAt,
237
275
  consolidatedAt: nowFn(),
238
276
  }
239
277
  const v = validateEpisodePre(ep)
240
278
  if (!v.ok) { current = null; return { ok: false, reason: 'invalid:' + v.reason } }
241
- episodes.push(v.episode)
279
+ // ★ P2-11 修复(2026-09-21):巩固入店前按 **episodeId 去重**。
280
+ // episodeId = hash(sessionRef, startedAt) ⇒ 同一会话在同一时间戳被重复巩固
281
+ // (或 restore/import 已带入同 id 记录)会产出**同一主键的第二条**,
282
+ // 下游 query/statsFor 会把一次会话数成两次 ⇒ distinctSessions/success 读数虚高。
283
+ // 去重策略:**后到者胜**(原地替换,不打乱既有顺序),与 fact-store restore 一致。
284
+ const dupAt = episodes.findIndex((x) => x.episodeId === v.episode.episodeId)
285
+ if (dupAt >= 0) episodes[dupAt] = v.episode
286
+ else episodes.push(v.episode)
242
287
  // 保留策略: 超 retention 淘汰最旧
243
288
  if (episodes.length > cfg.retention) {
244
289
  episodes = episodes.slice(-cfg.retention)
@@ -246,8 +291,10 @@ export function createEpisodicStorePre(opts = {}) {
246
291
  }
247
292
  current = null
248
293
  stats.consolidated++
249
- void persist()
250
- return { ok: true, episode: v.episode }
294
+ const pr = persist()
295
+ return pr.ok
296
+ ? { ok: true, episode: v.episode, persisted: true }
297
+ : { ok: true, episode: v.episode, persisted: false, persistError: pr.error }
251
298
  }
252
299
 
253
300
  /** 会话中途强制巩固(跨天续接/会话切换时调用)。 */
@@ -264,14 +311,14 @@ export function createEpisodicStorePre(opts = {}) {
264
311
  (q.sessionRef === undefined || e.sessionRef === q.sessionRef) &&
265
312
  (q.outcome === undefined || e.outcome === q.outcome) &&
266
313
  (q.success === undefined || e.success === q.success))
267
- .map((e) => ({ ...e }))
314
+ .map(cloneEpisode)
268
315
  }
269
316
  function recent(n = 10) {
270
- return episodes.slice(-n).map((e) => ({ ...e }))
317
+ return episodes.slice(-n).map(cloneEpisode)
271
318
  }
272
319
  function get(episodeId) {
273
320
  const e = episodes.find((x) => x.episodeId === episodeId)
274
- return e ? { ...e } : null
321
+ return e ? cloneEpisode(e) : null
275
322
  }
276
323
 
277
324
  // ---- M-04 挂钩: 供 Procedure 晋升用的事故/成功统计 ----
@@ -290,10 +337,12 @@ export function createEpisodicStorePre(opts = {}) {
290
337
  return {
291
338
  schemaVersion: 1, namespace: 'dsh-auto-memory', policyVersion: EPISODIC_POLICY_VERSION,
292
339
  savedAt: nowFn(),
293
- episodes: episodes.map((e) => ({ ...e })),
340
+ // ★ P2-2 修复:快照数组字段同样做副本(调用方改快照不得改脏库内 episode)
341
+ episodes: episodes.map(cloneEpisode),
294
342
  current: current ? {
295
343
  sessionRef: current.sessionRef, startedAt: current.startedAt,
296
- segments: current.segments, userTexts: current.userTexts, assistantTexts: current.assistantTexts,
344
+ segments: current.segments.map((s) => ({ ...s })),
345
+ userTexts: [...current.userTexts], assistantTexts: [...current.assistantTexts],
297
346
  } : null,
298
347
  }
299
348
  }
@@ -344,22 +393,47 @@ export function createEpisodicStorePre(opts = {}) {
344
393
  }
345
394
  function clear() {
346
395
  episodes = []; current = null
347
- try { io.clear() } catch (_) {}
348
- return { ok: true }
396
+ // ★ A-8:io.clear() 失败不得静默(残留快照会在下次 load 时"复活"已清空的 episode)
397
+ let cleared = true, clearError
398
+ try { io.clear() } catch (e) {
399
+ cleared = false
400
+ clearError = (e && e.message) ? String(e.message) : String(e)
401
+ }
402
+ return clearError !== undefined ? { ok: true, cleared, error: clearError } : { ok: true, cleared }
349
403
  }
350
404
  function dispose(reason) {
351
- if (disposed) return
405
+ if (disposed) return { ok: true, persisted: true, alreadyDisposed: true }
352
406
  disposed = true
353
- try { io.save(snapshot()) } catch (_) {}
407
+ // ★ A-8:dispose 是最后一次落盘机会,失败必须外显
408
+ const r = persist()
409
+ return { ok: r.ok, persisted: r.ok, ...(r.error ? { error: r.error } : {}) }
354
410
  }
355
411
 
412
+ /**
413
+ * ★ A-8 修复(2026-09-21):旧实现 `try { io.save(snapshot()) } catch (_) {}` **吞掉落盘失败** ——
414
+ * 调用方看到 ok:true、宿主继续推进状态,但磁盘没写上,该状态**永不重写**(静默数据丢失)。
415
+ * 现改为:① 返回结构化结果 `{ok, persisted}`(失败时附 error 文案); ② 失败记进模块内可读状态
416
+ * `lastPersistError`(经 `getLastPersistError()` 暴露)。不引入任何新 import。
417
+ * @returns {{ok:boolean, persisted:boolean, error?:string}}
418
+ */
419
+ let lastPersistError = null
356
420
  function persist() {
357
- try { io.save(snapshot()) } catch (_) {}
421
+ try {
422
+ io.save(snapshot())
423
+ lastPersistError = null
424
+ return { ok: true, persisted: true }
425
+ } catch (e) {
426
+ const msg = (e && e.message) ? String(e.message) : String(e)
427
+ lastPersistError = msg
428
+ return { ok: false, persisted: false, error: msg }
429
+ }
358
430
  }
359
431
 
360
432
  return {
361
433
  append, consolidate, flush, query, recent, get, statsFor,
362
434
  snapshot, restore, importEpisodes, clear, dispose,
435
+ /** ★ A-8:最近一次落盘失败(字符串)或 null —— 供宿主诊断"写盘失败但流程继续"的静默缺口。 */
436
+ getLastPersistError: () => lastPersistError,
363
437
  getStats: () => ({ ...stats }),
364
438
  get size() { return episodes.length },
365
439
  get hasCurrent() { return !!current },