@a9i5k4/dsh-auto-memory 0.1.7 → 0.1.8

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
@@ -233,6 +233,7 @@ window.__ModuleLoader__.load({
233
233
  note: '/api/dsh-auto-memory/note',
234
234
  external: '/api/dsh-auto-memory/external',
235
235
  externalImport: '/api/dsh-auto-memory/external-import',
236
+ summarize: '/api/dsh-auto-memory/summarize',
236
237
  }
237
238
  function query(params) {
238
239
  var search = new URLSearchParams()
@@ -399,10 +400,22 @@ window.__ModuleLoader__.load({
399
400
  }
400
401
  var rows = []
401
402
  rows.push(h('div', { 'data-dam-content': '' }, greetLine))
402
- // 抽屉列表
403
+ // 抽屉列表(AI 生活化总结缓存)
404
+ var sumCachePair = useState({})
405
+ var sumCache = sumCachePair[0]
406
+ var setSumCache = sumCachePair[1]
407
+ function requestSummary(key, period) {
408
+ if (sumCache[key] !== undefined) return
409
+ apiPost(API.summarize, { period: period }).then(function (d) {
410
+ if (d && d.result) { var n = Object.assign({}, sumCache); n[key] = d.result; setSumCache(n) }
411
+ }).catch(function () {})
412
+ }
403
413
  for (var d = 0; d < drawers.length; d++) {
404
414
  (function (dr) {
405
415
  var isOpen = openMap[dr.key] !== undefined ? openMap[dr.key] : dr.defaultOpen
416
+ if (isOpen && sumCache[dr.key] === undefined) {
417
+ requestSummary(dr.key, dr.key === 'yesterday' ? '昨天' : dr.key.replace('seg-', ''))
418
+ }
406
419
  rows.push(h('div', { key: dr.key, style: { marginTop: '8px' } },
407
420
  h('button', {
408
421
  'data-dam-btn': '',
@@ -412,6 +425,7 @@ window.__ModuleLoader__.load({
412
425
  h('span', { style: { fontWeight: 600, fontSize: '12.5px' } }, (isOpen ? '▼ ' : '▶ ') + dr.label),
413
426
  h('span', { 'data-dam-muted': '', style: { fontSize: '11.5px' } }, dr.items.length + t('logEntries'))),
414
427
  isOpen ? h('div', { style: { padding: '6px 4px 2px 10px' } },
428
+ sumCache[dr.key] ? h('div', { style: { marginBottom: '8px', padding: '8px 10px', borderRadius: '8px', fontSize: '12.5px', lineHeight: 1.65, background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 8%, transparent)', borderLeft: '3px solid var(--dsw-alias-brand-primary, #4f7cff)' } }, sumCache[dr.key]) : null,
415
429
  dr.items.map(function (en) {
416
430
  return h('div', { key: (en.time || '') + en.text, style: { marginBottom: '3px', fontSize: '12.5px', lineHeight: 1.5 } },
417
431
  dr.withTime && en.time ? h('span', { 'data-dam-muted': '', style: { fontFamily: 'monospace', fontSize: '11px', marginRight: '6px' } }, en.time) : null,
package/lib/index.js CHANGED
@@ -24,7 +24,7 @@ import path from 'node:path'
24
24
  export const name = 'auto-memory'
25
25
 
26
26
  /** Services required before the memory surfaces can mount. */
27
- export const inject = ['webServer', 'tools', 'systemPrompt']
27
+ export const inject = ['webServer', 'tools', 'systemPrompt', 'subagents']
28
28
 
29
29
  /** Prompt order of the memory section (before the tool-guidance band 100+). */
30
30
  const SECTION_ORDER = 90
@@ -45,6 +45,7 @@ export const API = {
45
45
  external: '/api/dsh-auto-memory/external',
46
46
  'external-import': '/api/dsh-auto-memory/external-import',
47
47
  calendar: '/api/dsh-auto-memory/calendar',
48
+ summarize: '/api/dsh-auto-memory/summarize',
48
49
  }
49
50
 
50
51
  const DEFAULT_CONFIG = {
@@ -341,13 +342,18 @@ class MemoryEngine {
341
342
  lines.push('\n[日历与日程(未完成)]\n' + calLines + '\n主动关注这些安排:对话中若提及相关时间点,主动用 calendar_add 补充新事项、calendar_done 标记完成;回复正文中向用户转述日历变更。')
342
343
  }
343
344
  }
345
+ // 暂离回来提示:距上次活动>1小时,要求 agent 在回复开头写欢迎语并提示打开记忆窗口
346
+ if (this._lastActiveAt && Date.now() - this._lastActiveAt > 3600000) {
347
+ lines.push('\n[欢迎回来]')
348
+ lines.push('用户离开已超过 1 小时(暂离/下班后回来)。在本轮回复的开头,先用一句简短温暖的话欢迎用户回来(如"欢迎回来!你离开的这段时间,我已经帮你把日志整理好了。"),然后提示"自动记忆窗口将打开,方便你了解这段时间的状况"(如已由 GUI 弹出概览则不必重复提示)。语气自然,一两句即可,不要长篇大论。')
349
+ }
344
350
  lines.push('\n[记忆写入纪律 — 必须遵守]')
345
351
  lines.push('- 会话开始:若任务与历史工作/历史决策相关,先回顾以上记忆;**遇到不熟悉的代码、领域或项目时,主动调用 memory_recall 检索本机所有 AI 工具的历史记忆(CodeBuddy/Claude Code/Codex 等会话),不要凭空猜测**。')
346
352
  lines.push('- 新工作区(无历史日志/笔记):主动用 memory_recall 探索本机历史,判断该项目是否曾在其他 AI 工具中工作过;也可调用 memory_external 查看并接入外部记忆;检索时在正文中说明"我先查一下之前的记录"。')
347
353
  lines.push('- 完成实质性工作后立即调用 memory_log 追加今日日志(append-only,绝不覆盖):建/改应用、修 bug、写文档、重构、技术选型、用户约定或偏好。')
348
354
  lines.push('- progress 与 memory 一起写:写日志的同时,把有跨会话长期价值的内容一并写入记忆——跨项目规则 → memory_user,仅本项目 → memory_note;两者在同一轮完成,互不冲突、不遗漏。')
349
355
  lines.push('- 只记录有跨会话长期价值的;不记临时信息(搜索结果、临时路径、工具报错)。')
350
- lines.push('- **记忆操作必须在正文可见(摘要链)**:调用 memory_log/note/user/reflect 更新记忆后,必须把结果写进本轮回复的正文文本(用户直接看到的那段文字,不是工具调用区),例如:"已更新今日日志,新增:修复了XXX";调用 memory_recall/memory_external 检索时,同样在正文开头写明"我查了记忆,发现..."。工具返回值只是辅助,正文转述是强制要求。')
356
+ lines.push('- **记忆操作必须在正文可见(摘要链)**:调用 memory_log/note/user/reflect 更新记忆后,必须把结果写进本轮回复的正文文本(用户直接看到的那段文字,不是工具调用区),并在**回复末尾**用加粗或换行使其醒目(如"**已更新今日日志**\n新增:修复了XXX");调用 memory_recall/memory_external 检索时,在正文开头写明"我查了记忆,发现..."。工具返回值只是辅助,正文转述是强制要求。')
351
357
  lines.push('- 用户明确要求长期记住:跨项目规则 → memory_user;仅本项目 → memory_note。')
352
358
  lines.push('- 定期调用 memory_maintain 归档 30 天前日志;不存密钥,除非用户明确要求。')
353
359
  lines.push('- 记忆仅作补充,不替代正常回复与交付物。')
@@ -587,6 +593,36 @@ class MemoryEngine {
587
593
  return { entries, groups, todayDate: todayStr() }
588
594
  }
589
595
 
596
+ /** 生活化总结:调用 DSH 的 AI(subagent)发散地总结某时段的工作(完成/收尾/烦恼/暂停的事)。 */
597
+ async summarizePeriod(period, agent) {
598
+ const ps = this.periodSummary()
599
+ const groups = ps.groups || {}
600
+ const items = (period === '昨天') ? (this.state.recentLogs[0] ? this.state.recentLogs[0].text.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('- ')).map((l) => l.replace(/^- /, '')) : []) : (groups[period] || [])
601
+ if (!items.length) return ''
602
+ const subagents = this._subagents
603
+ if (!subagents) return '' // 无 subagents 服务时前端只显示条目
604
+ const parent = agent && agent.session ? agent.session.id : undefined
605
+ const prompt = [
606
+ '你是一个温暖、细腻的生活助理。请根据下面这位用户的时段工作记录,写一段自然、口语化的总结(80-160字),像朋友聊天一样:',
607
+ '- 概括这个时段完成了什么(挑重要的说,不用逐条罗列)',
608
+ '- 提到是否收尾/完成(如果有明显没做完的迹象就轻轻点一下,不要断言)',
609
+ '- 如果看得出可能的烦恼或卡住的事,温和地提一句;看不出就跳过',
610
+ '- 语气轻松自然,不要列表、不要小标题、不要"总结:"前缀',
611
+ '',
612
+ '用户时段工作记录(' + period + '):',
613
+ items.map((x) => '- ' + x).join('\n'),
614
+ ].join('\n')
615
+ try {
616
+ const run = await subagents.start('spawn', { label: 'auto-memory-summarize', prompt, ...(parent ? { parent } : {}) })
617
+ const result = await run
618
+ const text = result && result.text ? String(result.text) : ''
619
+ return text.trim()
620
+ } catch (e) {
621
+ console.error('[dsh-auto-memory] summarize failed', e && e.message ? e.message : e)
622
+ return ''
623
+ }
624
+ }
625
+
590
626
  /** 一键反思:自动取"有日志但无反思"的最早日期,按日志条目生成反思草稿并落盘。 */
591
627
  async reflectAuto(agent) {
592
628
  const p = await this.resolvePaths(agent)
@@ -995,7 +1031,7 @@ async function globOne(dir, re, limit) {
995
1031
  return out
996
1032
  }
997
1033
 
998
- /** 从一条 jsonl 会话行提取文本片段(兼容 claude/codex/workbuddy 格式)。 */
1034
+ /** 从一条 jsonl 会话行提取文本片段(兼容 claude/codex 等格式)。 */
999
1035
  function extractJsonText(line) {
1000
1036
  try {
1001
1037
  const obj = JSON.parse(line)
@@ -1027,12 +1063,17 @@ export function apply(ctx, config) {
1027
1063
  const engine = new MemoryEngine()
1028
1064
  const sessionQuery = ctx.get('sessionQuery')
1029
1065
  engine._sessionQuery = sessionQuery
1066
+ engine._subagents = ctx.get('subagents') || undefined
1030
1067
 
1031
1068
  // 生命周期刷新
1032
1069
  const refreshAll = (agent) => { void engine.refresh(agent) }
1033
1070
  refreshAll()
1034
1071
  ctx.on('agent/session-start', (payload) => refreshAll(payload && payload.agent))
1035
- ctx.on('agent/turn-stopping', (payload) => refreshAll(payload && payload.agent))
1072
+ ctx.on('agent/turn-stopping', (payload) => {
1073
+ refreshAll(payload && payload.agent)
1074
+ // 记录最后活动时间(判断暂离回来用)
1075
+ try { engine._lastActiveAt = Date.now() } catch (e) {}
1076
+ })
1036
1077
 
1037
1078
  // ---------- 系统提示词注入 ----------
1038
1079
  const disposeSection = ctx.systemPrompt.section({
@@ -1355,6 +1396,17 @@ export function apply(ctx, config) {
1355
1396
  writeJson(res, 405, { error: 'method not allowed' })
1356
1397
  },
1357
1398
  },
1399
+ {
1400
+ kind: 'exact',
1401
+ path: API.summarize,
1402
+ handler: async (req, res) => {
1403
+ if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
1404
+ if ((req.method || 'POST') !== 'POST') return writeJson(res, 405, { error: 'method not allowed' })
1405
+ const body = await readJsonBody(req)
1406
+ if (!body || typeof body.period !== 'string') return writeJson(res, 400, { error: 'invalid body' })
1407
+ try { writeJson(res, 200, { result: await engine.summarizePeriod(body.period) }) } catch (e) { writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
1408
+ },
1409
+ },
1358
1410
  ]
1359
1411
 
1360
1412
  // ---------- 注册与清理 ----------
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.7",
4
+ "version": "0.1.8",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {