@a9i5k4/dsh-auto-memory 0.1.20 → 0.1.22

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/lib/client.js CHANGED
@@ -324,6 +324,22 @@ window.__ModuleLoader__.load({
324
324
 
325
325
  // ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
326
326
  var CHANGELOG = {
327
+ '0.1.22': { zh: [
328
+ '修复:更新说明可能被公告、欢迎或自动总结弹窗覆盖,导致升级后没有看到 changelog。',
329
+ '优化:更新说明现在优先展示并排队其他弹窗,只有点击“知道了”后才标记为已读;未确认时重启仍会再次显示。',
330
+ ], en: [
331
+ 'Fix: the update changelog could be replaced by notice, welcome, or summary dialogs during startup.',
332
+ 'Polish: update notes now take priority and queue other dialogs; the version is marked seen only after acknowledgement, so it appears again after restart when unconfirmed.',
333
+ ] },
334
+ '0.1.21': { zh: [
335
+ '上下文与缓存:动态记忆改为运行时快照,静态规则保持稳定;切换模型、跨天和记忆刷新不再反复击穿前缀缓存。',
336
+ '记忆系统:三层记忆、普通检索与多关键词 recall、自动沉淀已完成运行时验证,写入、读取和检索链路稳定。',
337
+ '可靠性与成本:修复重启后与会话消息提取问题;自动沉淀增加最小内容门槛、冷却时间、每日上限和反递归保护,减少无效子代理调用。',
338
+ ], en: [
339
+ 'Context and cache: dynamic memory now uses runtime snapshots while static rules stay stable; model switches, day changes, and refreshes no longer repeatedly break prefix caching.',
340
+ 'Memory system: three-layer memory, standard search, multi-keyword recall, and auto-consolidation have passed runtime verification for writing, reading, and retrieval.',
341
+ 'Reliability and cost: fixed restart and session-message extraction issues; auto-consolidation now has a content threshold, cooldown, daily cap, and recursion guard to reduce unnecessary subagent calls.',
342
+ ] },
327
343
  '0.1.19': { zh: [
328
344
  '稳定版:整合 0.1.16~0.1.19 全部修复与优化',
329
345
  '核心:上下文缓存策略重写——记忆注入迁至运行时上下文快照,系统提示词保持稳定,DeepSeek 前缀缓存全程命中,不再白白消耗 token(命中率恢复 95%+)',
@@ -384,9 +400,35 @@ window.__ModuleLoader__.load({
384
400
  '0.1.10': { zh: ['自动检查更新 + 设置页一键更新', '日界:凌晨的活儿归前一天(默认 7:30)', '记忆根目录系统文件夹选择器,换位置自动迁移', '每日写入预算,超限自动压缩旧内容', '30 天 AI 蒸馏'], en: ['Auto update check + one-click update in settings', 'Day boundary: late-night work logs to yesterday (default 07:30)', 'Native OS folder picker for memory root, auto-migration', 'Daily write budget with auto-compaction', '30-day AI distillation'] },
385
401
  }
386
402
  var dialogListeners = new Set()
387
- var dialogState = null // {kind:'update', versions:[...]} | {kind:'first'}
388
- function openDialog(d) { dialogState = d; dialogListeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
389
- function closeDialog() { dialogState = null; dialogListeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
403
+ var dialogState = null // 当前展示的弹窗
404
+ var dialogQueue = [] // 启动期多个异步弹窗按优先级排队,不相互覆盖
405
+ function dialogPriority(d) {
406
+ if (!d) return 0
407
+ if (d.kind === 'update') return 100
408
+ if (d.kind === 'first') return 90
409
+ if (d.kind === 'notice') return d.notice && d.notice.level === 'urgent' ? 80 : 70
410
+ if (d.kind === 'summary') return 60
411
+ if (d.kind === 'welcomeBack') return 50
412
+ return 10
413
+ }
414
+ function dialogKey(d) {
415
+ if (!d) return ''
416
+ if (d.kind === 'update') return 'update:' + (d.currentVersion || '')
417
+ if (d.kind === 'notice') return 'notice:' + ((d.notice && d.notice.id) || '')
418
+ if (d.kind === 'summary') return 'summary:' + ((d.summary && d.summary.date) || '') + ':' + ((d.summary && d.summary.time) || '')
419
+ return d.kind
420
+ }
421
+ function notifyDialog() { dialogListeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
422
+ function openDialog(d) {
423
+ if (!d) return
424
+ var key = dialogKey(d)
425
+ if ((dialogState && dialogKey(dialogState) === key) || dialogQueue.some(function (x) { return dialogKey(x) === key })) return
426
+ if (!dialogState) dialogState = d
427
+ else if (dialogPriority(d) > dialogPriority(dialogState)) { dialogQueue.unshift(dialogState); dialogState = d }
428
+ else dialogQueue.push(d)
429
+ notifyDialog()
430
+ }
431
+ function closeDialog() { dialogState = dialogQueue.shift() || null; notifyDialog() }
390
432
  function onDialog(fn) { dialogListeners.add(fn); return function () { dialogListeners.delete(fn) } }
391
433
  function cmpVersion(a, b) {
392
434
  var pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number)
@@ -1350,7 +1392,13 @@ window.__ModuleLoader__.load({
1350
1392
  h('div', { style: sub }, t('guideSub')),
1351
1393
  feats.map(function (f) { return h('div', { style: item }, h('span', { style: dot }), f) }),
1352
1394
  h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', opacity: .8, marginTop: '4px' } }, t('guideTip')),
1353
- h('button', { 'data-dam-btn': '', style: close, onClick: closeDialog }, t('gotIt'))))
1395
+ h('button', { 'data-dam-btn': '', style: close, onClick: function () {
1396
+ try {
1397
+ localStorage.setItem('dsh-auto-memory.firstRunDone', '1')
1398
+ if (dialogState && dialogState.currentVersion) localStorage.setItem('dsh-auto-memory.seenVersion', dialogState.currentVersion)
1399
+ } catch (e3) {}
1400
+ closeDialog()
1401
+ } }, t('gotIt'))))
1354
1402
  }
1355
1403
  if (dialogState.kind === 'notice') {
1356
1404
  var n = dialogState.notice || {}
@@ -1407,7 +1455,10 @@ window.__ModuleLoader__.load({
1407
1455
  h('div', { style: { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, margin: '6px 0 2px', opacity: .9 } }, 'v' + v.version),
1408
1456
  items.map(function (it) { return h('div', { style: item }, h('span', { style: dot }), it) }))
1409
1457
  }),
1410
- h('button', { 'data-dam-btn': '', style: close, onClick: closeDialog }, t('gotIt'))))
1458
+ h('button', { 'data-dam-btn': '', style: close, onClick: function () {
1459
+ try { if (lastV) localStorage.setItem('dsh-auto-memory.seenVersion', lastV) } catch (e3) {}
1460
+ closeDialog()
1461
+ } }, t('gotIt'))))
1411
1462
  }
1412
1463
 
1413
1464
  // ───────────────────────── 设置页 ─────────────────────────
@@ -1634,13 +1685,14 @@ window.__ModuleLoader__.load({
1634
1685
  try {
1635
1686
  var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
1636
1687
  if (!seen && !localStorage.getItem('dsh-auto-memory.firstRunDone')) {
1637
- try { localStorage.setItem('dsh-auto-memory.firstRunDone', '1') } catch (e2) {}
1638
- openDialog({ kind: 'first' })
1688
+ openDialog({ kind: 'first', currentVersion: d.current })
1639
1689
  } else if (seen && seen !== d.current) {
1640
1690
  var versions = changelogBetween(seen, d.current)
1641
- if (versions.length) openDialog({ kind: 'update', versions: versions })
1691
+ if (versions.length) openDialog({ kind: 'update', versions: versions, currentVersion: d.current })
1692
+ else try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
1693
+ } else if (!seen && localStorage.getItem('dsh-auto-memory.firstRunDone')) {
1694
+ try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
1642
1695
  }
1643
- try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
1644
1696
  } catch (e) {}
1645
1697
  }
1646
1698
  }).catch(function () {})
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * - 路由:/api/dsh-auto-memory/{state,list,file,recall,config,reflect}(loopback-only)
18
18
  */
19
19
 
20
- import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile } from 'node:fs/promises'
20
+ import { readFile, writeFile, mkdir, readdir, stat, rm, copyFile, appendFile } from 'node:fs/promises'
21
21
  import { createReadStream, existsSync } from 'node:fs'
22
22
  import { exec as cpExec } from 'node:child_process'
23
23
  import { promisify } from 'node:util'
@@ -82,7 +82,11 @@ const DEFAULT_CONFIG = {
82
82
  /** 每轮对话结束自动沉淀记忆(subagent 判断+提炼,有 API 成本;默认开)。 */
83
83
  autoConsolidate: true,
84
84
  /** 自动沉淀内容门槛:本轮 user+assistant 文本总字符数低于此值视为寒暄,跳过。 */
85
- autoConsolidateMinChars: 60,
85
+ autoConsolidateMinChars: 240,
86
+ /** 自动沉淀冷却分钟:避免连续短轮反复调用 subagent。 */
87
+ autoConsolidateCooldownMinutes: 5,
88
+ /** 自动沉淀每日最多调用次数(跨插件实例应只保留一个实例)。 */
89
+ autoConsolidateDailyMax: 8,
86
90
  /** 暂离阈值(分钟):距上次活动超过该值视为暂离,回归时自动弹出记忆窗口并欢迎。默认 60。 */
87
91
  awayMinutes: 60,
88
92
  /** 自动总结时间点(24h "HH:MM" 数组,如 ["12:00","18:00","22:00"]):到点自动生成本时段总结并弹窗展示。空数组=关闭。 */
@@ -136,6 +140,16 @@ function dshHome() {
136
140
  return path.join(homedir(), '.dsh')
137
141
  }
138
142
 
143
+ /** 诊断输出:写 ~/.dsh/dsh-auto-memory-diagnose.log(append)+console.log 双保险。验证完移除。 */
144
+ let _diagChain = Promise.resolve()
145
+ function diag(msg) {
146
+ try {
147
+ const line = new Date().toISOString() + ' ' + msg + '\n'
148
+ _diagChain = _diagChain.then(() => appendFile(path.join(dshHome(), 'dsh-auto-memory-diagnose.log'), line, 'utf8')).catch(() => {})
149
+ console.log('[dsh-auto-memory] ' + msg)
150
+ } catch (e) {}
151
+ }
152
+
139
153
  /** 记忆引擎:路径解析、缓存、文件读写、检索、反思状态。 */
140
154
  class MemoryEngine {
141
155
  constructor() {
@@ -158,6 +172,9 @@ class MemoryEngine {
158
172
  this._lastAgent = undefined // 最近一次 agent 引用(subagent parent 需要完整 agent 对象)
159
173
  this._lastTurnByAgent = undefined // Map<agentId, turn>:自动沉淀去重(每轮只写一次)
160
174
  this._consolidating = undefined // 自动沉淀进行中标记(防重入)
175
+ this._autoCallDate = ''
176
+ this._autoCallCount = 0
177
+ this._lastConsolidateStartedAt = 0
161
178
  this._budgets = undefined // 每日写入预算:用户级4000/项目级3000字/天(所有会话共享,跨天重置)
162
179
  this.autoStats = { count: 0, lastAt: 0, lastText: '', lastDate: '' } // 自动沉淀统计(GUI 即时反馈)
163
180
  this.external = new ExternalMemory(this)
@@ -1187,7 +1204,10 @@ class MemoryEngine {
1187
1204
  heartbeat,
1188
1205
  autoConsolidate: {
1189
1206
  enabled: this.config.autoConsolidate !== false,
1190
- minChars: Math.max(Number(this.config.autoConsolidateMinChars) || 60, 20),
1207
+ minChars: Math.max(Number(this.config.autoConsolidateMinChars) || 240, 80),
1208
+ cooldownMinutes: Math.max(Number(this.config.autoConsolidateCooldownMinutes) || 5, 1),
1209
+ dailyMax: Math.max(Number(this.config.autoConsolidateDailyMax) || 8, 1),
1210
+ callCountToday: this._autoCallCount || 0,
1191
1211
  consolidating: !!this._consolidating,
1192
1212
  pendingQueue: (this._pendingConsolidations || []).length,
1193
1213
  stats: this.autoStats,
@@ -1429,9 +1449,9 @@ class MemoryEngine {
1429
1449
  try {
1430
1450
  const sessionsSvc = this._sessionsSvc
1431
1451
  const agentSvc = this._agentSvc
1432
- if (!sessionsSvc || !agentSvc || typeof sessionsSvc.list !== 'function') return
1452
+ if (!sessionsSvc || !agentSvc || typeof sessionsSvc.list !== 'function') { diag('restoreLastAgent: svc missing sessions=' + !!sessionsSvc + ' agent=' + !!agentSvc); return }
1433
1453
  const sessions = sessionsSvc.list()
1434
- if (!sessions || !sessions.length) return
1454
+ if (!sessions || !sessions.length) { diag('restoreLastAgent: no sessions'); return }
1435
1455
  // 找最近活跃的顶层会话(log 最后事件时间最大)
1436
1456
  let best = null, bestTime = 0
1437
1457
  for (const s of sessions) {
@@ -1439,10 +1459,15 @@ class MemoryEngine {
1439
1459
  try { const last = s.log && s.log[s.log.length - 1]; if (last && last.time) t = last.time } catch (e) {}
1440
1460
  if (t >= bestTime) { bestTime = t; best = s }
1441
1461
  }
1442
- if (!best || typeof agentSvc.get !== 'function') return
1462
+ if (!best || typeof agentSvc.get !== 'function') { diag('restoreLastAgent: no best session or no get'); return }
1443
1463
  const a = agentSvc.get(best.id)
1444
- if (a && a.session && a.session.header && a.session.header.parentSession === undefined) this._lastAgent = a
1445
- } catch (e) {}
1464
+ if (a && a.session && a.session.header && a.session.header.parentSession === undefined) {
1465
+ this._lastAgent = a
1466
+ diag('restoreLastAgent: recovered agent id=' + a.id + ' session=' + a.session.id + ' logEvents=' + ((a.session.events && a.session.events.length) || 0))
1467
+ } else {
1468
+ diag('restoreLastAgent: candidate rejected (id=' + (a && a.id) + ' hasSession=' + !!(a && a.session) + ' parent=' + (a && a.session && a.session.header && a.session.header.parentSession) + ')')
1469
+ }
1470
+ } catch (e) { diag('restoreLastAgent error: ' + (e && e.message)) }
1446
1471
  }
1447
1472
 
1448
1473
  /** 自动总结:按时间点推断时段,生成总结并置 pendingSummary(供 client 弹窗)。 */
@@ -1503,6 +1528,10 @@ class MemoryEngine {
1503
1528
  if (!subagents) return ''
1504
1529
  // parent 必须是完整 agent 对象(captureDelegatedPolicyOverrides 读 parent.ctx/parent.session);路由调用用缓存的最近 agent
1505
1530
  const parent = agent || this._lastAgent
1531
+ if (!parent || !parent.session || !parent.ctx || typeof parent.ctx.get !== 'function') {
1532
+ diag('subagent ' + label + ' skipped: incomplete parent context')
1533
+ return ''
1534
+ }
1506
1535
  // 动态选择可用的 subagent provider(优先 spawn,否则取已注册的第一个)
1507
1536
  let providerName = 'spawn'
1508
1537
  let registered = []
@@ -1524,7 +1553,9 @@ class MemoryEngine {
1524
1553
  const blocks = result && result.output ? result.output : []
1525
1554
  return blocks.filter((b) => b && b.type === 'text').map((b) => b.text).join('').trim()
1526
1555
  } catch (e) {
1527
- console.error('[dsh-auto-memory] ' + label + ' failed', e && e.message ? e.message : e)
1556
+ const em = e && e.message ? e.message : String(e)
1557
+ console.error('[dsh-auto-memory] ' + label + ' failed', em)
1558
+ diag('subagent ' + label + ' failed: ' + em)
1528
1559
  return ''
1529
1560
  } finally {
1530
1561
  clearTimeout(timer)
@@ -1545,31 +1576,41 @@ class MemoryEngine {
1545
1576
 
1546
1577
  /** 每轮对话结束自动沉淀:取本轮 user+assistant 消息 → subagent 判断/提炼 → 写今日日志+升格。 */
1547
1578
  async consolidateTurn(turn, agent) {
1548
- if (this._consolidating) return
1579
+ const why = (reason) => diag('consolidate skip: ' + reason + ' (turn=' + JSON.stringify(turn) + ' agentId=' + ((agent && (agent.id || (agent.session && agent.session.id))) || '?') + ')')
1580
+ if (this._consolidating) { why('_consolidating busy'); return }
1549
1581
  if (!this.configLoaded) { try { await this.loadConfig() } catch (e) {} }
1550
- if (this.config.autoConsolidate === false) return
1551
- if (!agent || !agent.session) return
1582
+ if (this.config.autoConsolidate === false) { why('config.autoConsolidate=false'); return }
1583
+ if (!agent || !agent.session) { why('no agent/session'); return }
1552
1584
  // 只处理顶层会话(子代理/接续会话的 header.parentSession 非空,避免子代理轮次误沉淀)
1553
- try { if (agent.session.header && agent.session.header.parentSession) return } catch (e) {}
1554
- const minChars = Math.max(Number(this.config.autoConsolidateMinChars) || 60, 20)
1585
+ try { if (agent.session.header && agent.session.header.parentSession) { why('parentSession sub-agent'); return } } catch (e) {}
1586
+ const minChars = Math.max(Number(this.config.autoConsolidateMinChars) || 240, 80)
1587
+ const today = this.memToday()
1588
+ if (this._autoCallDate !== today) { this._autoCallDate = today; this._autoCallCount = 0 }
1589
+ const cooldownMs = Math.max(Number(this.config.autoConsolidateCooldownMinutes) || 5, 1) * 60000
1590
+ const dailyMax = Math.max(Number(this.config.autoConsolidateDailyMax) || 8, 1)
1591
+ if (this._autoCallCount >= dailyMax) { why('daily subagent cap=' + dailyMax); return }
1592
+ if (this._lastConsolidateStartedAt && Date.now() - this._lastConsolidateStartedAt < cooldownMs) { why('cooldown'); return }
1555
1593
  // 按 turn 去重:同一 agent 的同一轮只处理一次
1556
1594
  const agentId = agent.id || (agent.session && agent.session.id) || '?'
1557
1595
  if (!this._lastTurnByAgent) this._lastTurnByAgent = new Map()
1558
1596
  const lastTurn = this._lastTurnByAgent.get(agentId)
1559
- if (lastTurn === turn) return
1597
+ if (lastTurn === turn) { why('dup turn'); return }
1560
1598
  this._lastTurnByAgent.set(agentId, turn)
1561
1599
  // 取本轮最后一条 user + 最后一条 assistant(模型可见消息序列)
1562
1600
  const messages = extractSessionMessages(agent)
1563
- if (messages.length < 2) return
1601
+ if (messages.length < 2) { why('messages<2 got=' + messages.length + ' seqs=' + ((agent.session.surface && agent.session.surface.nodes && (Array.isArray(agent.session.surface.nodes) ? agent.session.surface.nodes.length : 'set')) || 'none') + ' events=' + ((agent.session.events && agent.session.events.length) || 'none')); return }
1564
1602
  let userText = ''
1565
1603
  let assistantText = ''
1566
1604
  for (const m of messages) {
1567
1605
  if (m.role === 'user') userText = m.text
1568
1606
  else if (m.role === 'assistant' && m.text) assistantText = m.text
1569
1607
  }
1570
- if (!userText.trim() || !assistantText.trim()) return
1608
+ if (!userText.trim() || !assistantText.trim()) { why('empty text user=' + userText.length + ' asst=' + assistantText.length); return }
1571
1609
  const combined = userText + '\n' + assistantText
1572
- if (combined.length < minChars) return
1610
+ if (combined.length < minChars) { why('too short combined=' + combined.length + ' min=' + minChars); return }
1611
+ this._lastConsolidateStartedAt = Date.now()
1612
+ this._autoCallCount++
1613
+ diag('consolidate subagent start count=' + this._autoCallCount + '/' + dailyMax + ' inputChars=' + Math.min(combined.length, 6000))
1573
1614
  this._consolidating = (async () => {
1574
1615
  try {
1575
1616
  await this.refresh(agent)
@@ -1606,6 +1647,7 @@ class MemoryEngine {
1606
1647
  ].join('\n')
1607
1648
  const text = await this.runSubagent(prompt, 'auto-memory-consolidate', agent)
1608
1649
  if (!text) {
1650
+ diag('consolidate: subagent returned empty text (queued for retry)')
1609
1651
  // subagent 失败(返回空):入重试队列,由后台轮询兜底重试
1610
1652
  if (!this._pendingConsolidations) this._pendingConsolidations = []
1611
1653
  if (this._pendingConsolidations.length < 5) this._pendingConsolidations.push({ turn, agent })
@@ -2220,28 +2262,46 @@ class ExternalMemory {
2220
2262
  }
2221
2263
  }
2222
2264
 
2223
- /** 从 agent.session 提取模型可见消息序列 [{role,text,sourceKind}]。 */
2265
+ /** 从 session 提取消息。surface 不是完整可靠的 user 来源,因此失败时回退完整事件日志。 */
2266
+ function messageOfEvent(ev) {
2267
+ if (!ev) return null
2268
+ if (ev.type === 'user/message') return ev.data && ev.data.message ? ev.data.message : ev.data
2269
+ if (ev.type === 'assistant/message' || ev.type === 'tool/result') return ev.data && ev.data.message
2270
+ return null
2271
+ }
2272
+ function textOfContent(content) {
2273
+ const out = []
2274
+ const walk = (v, depth) => {
2275
+ if (depth > 8 || v == null) return
2276
+ if (typeof v === 'string') { out.push(v); return }
2277
+ if (Array.isArray(v)) { for (const x of v) walk(x, depth + 1); return }
2278
+ if (typeof v !== 'object') return
2279
+ if (typeof v.text === 'string') out.push(v.text)
2280
+ else if (typeof v.input_text === 'string') out.push(v.input_text)
2281
+ if (v.content !== undefined) walk(v.content, depth + 1)
2282
+ }
2283
+ walk(content, 0)
2284
+ return out.join('')
2285
+ }
2224
2286
  function extractSessionMessages(agent) {
2225
2287
  try {
2226
2288
  const session = agent && agent.session
2227
2289
  if (!session) return []
2228
- const nodes = session.surface && session.surface.nodes
2229
- if (!nodes) return []
2230
- const seqs = Array.isArray(nodes) ? nodes : (nodes instanceof Set ? Array.from(nodes) : [])
2231
- if (!seqs.length) return []
2232
2290
  const events = session.events || []
2291
+ const seqs = []
2292
+ try {
2293
+ const nodes = session.surface && session.surface.nodes
2294
+ if (Array.isArray(nodes)) seqs.push(...nodes)
2295
+ else if (nodes instanceof Set) seqs.push(...Array.from(nodes))
2296
+ } catch (e) {}
2297
+ // surface 可能遗漏刚提交的 user/message;追加完整事件序列并去重。
2298
+ for (let i = 0; i < events.length; i++) if (!seqs.includes(i)) seqs.push(i)
2233
2299
  const out = []
2234
2300
  for (const seq of seqs) {
2235
2301
  const ev = events[seq]
2236
- if (!ev) continue
2237
- let msg = null
2238
- if (ev.type === 'user/message') msg = ev.data
2239
- else if (ev.type === 'assistant/message' || ev.type === 'tool/result') msg = ev.data && ev.data.message
2302
+ const msg = messageOfEvent(ev)
2240
2303
  if (!msg || !msg.role || !Array.isArray(msg.content)) continue
2241
- const text = msg.content
2242
- .filter((b) => b && typeof b === 'object' && typeof b.text === 'string')
2243
- .map((b) => b.text).join('')
2244
- out.push({ role: msg.role, text, sourceKind: msg.source && msg.source.kind })
2304
+ out.push({ role: msg.role, text: textOfContent(msg.content), sourceKind: msg.source && msg.source.kind })
2245
2305
  }
2246
2306
  return out
2247
2307
  } catch (e) { return [] }
@@ -2321,11 +2381,12 @@ export function apply(ctx, config) {
2321
2381
  // 记录最后活动时间(判断暂离回来用)
2322
2382
  try { engine._lastActiveAt = Date.now() } catch (e) {}
2323
2383
  // 每轮自动沉淀:取本轮消息 → subagent 判断/提炼 → 写今日日志([自动沉淀])+升格长期记忆
2324
- // 注意:turn-stopping payload 只有 {turn, signal},没有 agent 字段,必须用 _lastAgent 兜底
2325
2384
  try {
2326
2385
  const agent = (payload && payload.agent) || engine._lastAgent
2327
- if (agent && agent.session) void engine.consolidateTurn(payload.turn, agent)
2328
- } catch (e) {}
2386
+ const hasAgent = !!(agent && agent.session)
2387
+ diag('turn-stopping fired: turn=' + JSON.stringify(payload && payload.turn) + ' hasAgent=' + hasAgent + ' payloadKeys=' + (payload ? Object.keys(payload).join(',') : 'null') + ' _lastAgent=' + !!(engine._lastAgent && engine._lastAgent.session))
2388
+ if (hasAgent) void engine.consolidateTurn(payload.turn, agent)
2389
+ } catch (e) { diag('turn-stopping handler error: ' + (e && e.message)) }
2329
2390
  })
2330
2391
  // 注入层保障:systemPrompt section.text 是同步函数不能 await,state 异步加载会导致首轮注入为空。
2331
2392
  // pre-step 是 waterfall(可 await),在放行每个 step 前条件性刷新:
@@ -2335,7 +2396,7 @@ export function apply(ctx, config) {
2335
2396
  const agent = (payload && payload.agent) || engine._lastAgent
2336
2397
  // 兜底恢复 _lastAgent(仅启动后首次为空时设置一次,不每步覆盖):重启后恢复的会话不触发 session-start,
2337
2398
  // 导致 turn-stopping 自动沉淀拿不到 agent 而永久失效;首次 pre-step 即恢复,之后由 session-start 正常维护
2338
- try { if (!engine._lastAgent && agent && agent.session && agent.session.header && agent.session.header.parentSession === undefined) engine._lastAgent = agent } catch (e) {}
2399
+ try { if (!engine._lastAgent && agent && agent.session && agent.session.header && agent.session.header.parentSession === undefined) { engine._lastAgent = agent; diag('pre-step recovered _lastAgent id=' + agent.id) } } catch (e) {}
2339
2400
  // 只刷新顶层会话:子代理(自动沉淀/固化的 subagent)session 无 cwd,刷新会把 state 切到错误工作区
2340
2401
  let skip = false
2341
2402
  try { if (agent && agent.session && agent.session.header && agent.session.header.parentSession) skip = true } catch (e) {}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-auto-memory",
3
3
  "description": "DSH 自动记忆插件:三层记忆(用户级/项目笔记/每日日志)自动注入与检索、每轮对话自动沉淀、每日反思、可视化面板与设置页,支持继承其他 AI 工具的记忆。",
4
- "version": "0.1.20",
4
+ "version": "0.1.22",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {