@a9i5k4/dsh-auto-memory 3.1.0 → 3.1.3

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.
@@ -40,7 +40,30 @@ export function truncateAuditEvent(ev) {
40
40
  return e
41
41
  }
42
42
 
43
- export function createShadowHost({ engine }) {
43
+ /**
44
+ * ★P3-9(组2,2026-09-22):诊断**节流**表。
45
+ * 原先 `no-paths` 每个 Segment 都直接 console.error ⇒ 单次历史回放能刷出上百行。
46
+ * 表放**模块级**而非闭包级:同进程多实例共享 ⇒ 不会各自持一份而双写。
47
+ * 语义同 index.js 的 diagThrottled:首条立即透出,窗口内重复压掉。
48
+ */
49
+ const DIAG_THROTTLE_MS_V1 = 300000
50
+ const _diagAt = new Map()
51
+ function shouldReportDiag(key) {
52
+ try {
53
+ const now = Date.now()
54
+ if (now - (Number(_diagAt.get(key)) || 0) < DIAG_THROTTLE_MS_V1) return false
55
+ _diagAt.set(key, now)
56
+ } catch (_) { /* 计时失败不阻断上报 */ }
57
+ return true
58
+ }
59
+
60
+ /**
61
+ * @param {{engine: object, onDiag?: Function|null, recallStats?: object|null}} opts
62
+ * `recallStats` = 召回统计实例(可选)。**依赖注入而非模块级单例**:本模块保持零耦合,
63
+ * 测试可传假对象(不碰真磁盘);不传时埋点整段跳过,行为与从前逐字节相同。
64
+ * ★命名:不得叫 `stats` —— 本函数内部已有同名 const(shadow 自身计数器),会作用域冲突。
65
+ */
66
+ export function createShadowHost({ engine, onDiag = null, recallStats = null }) {
44
67
  const states = new WeakMap() // runtime → shadow state(lazy)
45
68
  const volatileRing = [] // ≤64 events(最小投影)
46
69
  const stats = { evaluated: 0, retrieved: 0, prefetched: 0, suppressed: 0, stale: 0, errors: 0 }
@@ -58,6 +81,13 @@ export function createShadowHost({ engine }) {
58
81
  engine.config.shadowRetrievalEnabled === true &&
59
82
  engine.config.memoryAnchorEnabled === true
60
83
 
84
+ /** ★P3-9(组2):节流上报。未注入 onDiag 时回落 console.error(保持既有行为,只去掉刷屏)。 */
85
+ function reportDiag(key, msg) {
86
+ if (!shouldReportDiag(key)) return
87
+ if (typeof onDiag === 'function') { try { onDiag(key, msg); return } catch (_) { /* 回落 */ } }
88
+ try { console.error(msg) } catch (_) {}
89
+ }
90
+
61
91
  function stateFor(runtime) {
62
92
  let st = states.get(runtime)
63
93
  if (!st) {
@@ -225,7 +255,7 @@ export function createShadowHost({ engine }) {
225
255
  return
226
256
  }
227
257
  const paths = pathsByKey.get(String(runtime.key || '')) || null
228
- if (!paths) { console.error('[shadow-diag] no-paths'); stats.suppressed++; pushVolatile({ reason: 'no-paths-captured', contextVersion: seg.contextVersion }); return }
258
+ if (!paths) { reportDiag('shadow:no-paths', '[shadow-diag] no-paths'); stats.suppressed++; pushVolatile({ reason: 'no-paths-captured', contextVersion: seg.contextVersion }); return }
229
259
  // window:segments ring 最近 8 条(含当前)
230
260
  const ringItems = runtime.segments ? runtime.segments.snapshot() : []
231
261
  const win = ringItems.slice(-8).map((w) => ({
@@ -320,6 +350,15 @@ export function createShadowHost({ engine }) {
320
350
  if (dec.action === 'retrieve') stats.retrieved++
321
351
  else stats.prefetched++
322
352
  st.recentHits.push({ queryDigest: qp.queryDigest, fresh: ls.kept.length > 0, memoryIds: ls.kept.map((k) => k.memoryId) })
353
+ // ★③ 召回统计 · shadow 通道(2026-09-22 用户拍板「分开计算」):
354
+ // 这条通路 = 系统**主动判断该唤起**,与「模型主动检索」是两回事,必须分账。
355
+ // 语义:命中率(kept 空 ⇒ 唤起了但没东西)+ 哪些内容被系统认为该唤起。
356
+ // ★只读:不改 ls / st / 决策,不影响任何 shadow 行为(守卫断言)。
357
+ if (recallStats && typeof recallStats.observe === 'function') {
358
+ try {
359
+ recallStats.observe(ls.kept.map((k) => ({ id: k.memoryId, layer: k.sourceClass || k.scope || 'shadow' })), { channel: 'shadow' })
360
+ } catch (_) {}
361
+ }
323
362
  if (st.recentHits.length > 64) st.recentHits.shift()
324
363
  st.completedKeys.add(qp.queryDigest + ':' + snapshotOut.memoryIndexVersion)
325
364
  if (st.completedKeys.size > 256) { const first = st.completedKeys.values().next().value; st.completedKeys.delete(first) }
@@ -201,7 +201,11 @@ export async function scanPluginSubagentSessions(opts = {}) {
201
201
  // 按 mtime 取最新存在的文件,兼容两代命名。
202
202
  let file = ''
203
203
  let st = null
204
- for (const name of ['session.v3.jsonl.zstd', 'session.jsonl.zstd']) {
204
+ // ★v3.1.3:不再固定候选名 —— 扫目录取任意 session.*\.jsonl[.zstd](兼容未来 vN 命名)。
205
+ let names = []
206
+ try { names = readdirSync(dir) } catch (_) { names = [] }
207
+ const hits = names.filter((n) => /^session[.-].*\.jsonl(\.zstd)?$/i.test(n) && !/backup|corrupt/i.test(n))
208
+ for (const name of (hits.length ? hits : ['session.v3.jsonl.zstd', 'session.jsonl.zstd'])) {
205
209
  const cand = path.join(dir, name)
206
210
  try {
207
211
  const s = await stat(cand)
package/lib/wb-sidecar.js CHANGED
@@ -668,7 +668,10 @@ export function buildKanbanPre(index, opts = {}) {
668
668
  source: String(c.source || ''),
669
669
  section: String(c.section || ''),
670
670
  preview: body.replace(/\s+/g, ' ').trim().slice(0, 400),
671
- full: body.slice(0, 6000), // P12: 卡上可展开看全文
671
+ // ★v3.1.2 性能:full 不再内联进看板载荷(实测每卡≤6000 字符、1178 卡合计 0.85 MB,
672
+ // 且 lanes+matrix 双份 ⇒ 1.7 MB 纯冗余;前端默认只渲染 preview,full 仅「展开全文」需要)。
673
+ // 改为只带长度标示,正文由 kanban-card 路由按 id 单取。
674
+ fullLen: body.length,
672
675
  tags: t.tags,
673
676
  sectionTags: t.sectionTags,
674
677
  cues: (Array.isArray(c.cues) ? c.cues : []).slice(0, 3),
@@ -698,7 +701,8 @@ export function buildKanbanPre(index, opts = {}) {
698
701
  source: String(e.source || ''),
699
702
  section: String(e.section || ''),
700
703
  preview: String(e.preview || '').slice(0, 400),
701
- full: String(e.preview || '').slice(0, 6000),
704
+ // ★v3.1.2 性能:同 sectionCards 分支 —— 不内联 full,仅带长度(见上)。
705
+ fullLen: String(e.preview || '').length,
702
706
  tags: t.tags,
703
707
  sectionTags: t.sectionTags,
704
708
  cues: (Array.isArray(e.cues) ? e.cues : []).slice(0, 3),
@@ -842,7 +846,8 @@ export function buildKanbanMatrixPre(cards, opts = {}) {
842
846
  docTitle: String(c.docTitle || ''),
843
847
  source: String(c.source || ''),
844
848
  preview: body.replace(/\s+/g, ' ').trim().slice(0, 220),
845
- full: body.slice(0, 6000),
849
+ // ★v3.1.2 性能:矩阵卡同样不内联 full(同 lanes 口径)——带长度标示,点开时按 id 单取。
850
+ fullLen: body.length,
846
851
  bullets: Number(c.bullets) || 0,
847
852
  criteria: String(c.criteria || 'unknown'),
848
853
  badge: criteriaBadgePre(c.criteria),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-auto-memory",
3
3
  "description": "Proactive associative memory for DSH: zero-prompt recall injected before the model speaks, three-layer auto-consolidation, skill crystallization, and Astra-style context management - handoff ledgers, PLAN whiteboard, water-level sensing. Local-first, model-agnostic, zero deps. 主动联想记忆+Astra 式上下文管理:自动唤回/自动沉淀/技能固化/交接账本与白板跨窗口续命/水位感知。",
4
- "version": "3.1.0",
4
+ "version": "3.1.3",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {