@a9i5k4/dsh-auto-memory 0.1.6 → 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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # dsh-auto-memory — DSH Auto Memory Plugin / DSH 自动记忆插件
2
2
 
3
- An auto-memory plugin for the DeepSeek Harness Web GUI: three-layer memory (user-level / project notes / daily logs) with automatic injection and retrieval, daily reflections, a visual panel and settings page, and inheritance of memories from other AI tools.
3
+ An auto-memory plugin for the DeepSeek Harness Web GUI: three-layer memory (user-level / project notes / daily logs) with automatic injection and retrieval, daily reflections, smart period greetings, a calendar view and settings page, and inheritance of memories from other AI tools.
4
4
 
5
- DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、可视化面板与设置页,支持继承其他 AI 工具的历史记忆。
5
+ DSH Web GUI 的记忆插件:三层记忆自动注入与检索、每日反思、智能时段问候、日历视图与设置页,支持继承其他 AI 工具的历史记忆。
6
6
 
7
7
  [**English**](README.md) | [中文版](README.zh-CN.md)
8
8
 
@@ -10,18 +10,45 @@ DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日
10
10
 
11
11
  ## Features
12
12
 
13
+ ### Three-layer Memory
14
+
13
15
  | Layer | Location | Description |
14
16
  |---|---|---|
15
17
  | User-level memory | `~/.dsh/memory/MEMORY.md` | Cross-project rules & preferences |
16
18
  | Project notes | `{workspace}/.dsh-memory/MEMORY.md` | Project conventions & decisions |
17
19
  | Daily logs | `{workspace}/.dsh-memory/YYYY-MM-DD.md` | Append-only work log |
18
- | Reflections | `{workspace}/.dsh-memory/reflections/YYYY-MM-DD.md` | Daily reflection, auto-prompted |
20
+ | Reflections | `{workspace}/.dsh-memory/reflections/YYYY-MM-DD.md` | Daily reflection (structured, kept in background) |
21
+
22
+ - **Auto injection**: injects a `<memory_system>` block into every system prompt (user rules + project notes + recent reflections + recent N days of log tails + pending calendar items + writing discipline)
23
+ - **Visible memory ops**: when the AI updates or searches memory, it says so in plain text in the chat reply (e.g. "Logged X to today's journal", "I checked memory and found..."), not hidden inside tool calls
24
+
25
+ ### Smart Period Greetings (Overview page)
26
+
27
+ The first thing you see when opening the memory panel is a friendly period greeting, not technical info:
28
+
29
+ - **Multiple times a day**: morning shows "yesterday summary + early morning", forenoon shows the morning's work, noon/afternoon/evening follow suit
30
+ - **Smart timing**: if you were away for more than 1 hour (off work / stepped away) and come back, it automatically shows "Welcome back" with what was finished meanwhile
31
+ - **Drawer layout**: the outer layer stays casual and greeting-like; each period is a drawer — tap to expand the professional details
32
+ - **Daily reflection stays in background**: structured reflections (results / lessons / next steps) are kept, while the front page only shows a light greeting
33
+
34
+ ### Calendar View (Four Quadrants)
35
+
36
+ New 「Calendar」tab (liquid-glass monthly view):
37
+
38
+ - Monthly grid, today highlighted, click any date to add an item
39
+ - **Four-quadrant colors**: Urgent & Important (red) / Important (blue) / Urgent (orange) / Neither (gray)
40
+ - Click an item to toggle done, click again to delete; legend + weekday header
41
+ - **Cross-conversation persistence**: data lives at user level `~/.dsh/memory/CALENDAR.md`, shared across workspaces, survives DSH reinstall
42
+ - **AI-maintained**: the AI extracts deadlines and appointments from conversations and writes them to the calendar automatically (`calendar_add` / `calendar_list` / `calendar_done` / `calendar_remove`), restating it in plain text; pending items are injected into every session's system prompt
43
+
44
+ ### Agent Tools
45
+
46
+ `memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect` / `calendar_add` / `calendar_list` / `calendar_done` / `calendar_remove`
47
+
48
+ ### UI
19
49
 
20
- - **Auto injection**: injects a `<memory_system>` block into every system prompt (user rules + project notes + recent reflections + recent N days of log tails + writing discipline)
21
- - **Daily reflection**: when yesterday has logs but no reflection, the agent presents one at session start
22
- - **Agent tools**: `memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect`
23
- - **GUI**: sidebar 「Memory」entry → floating panel (Overview / Logs / Notes / Reflections / Connect / Search); Settings page (Settings → Auto Memory)
24
- - **UI language**: switch between 中文 / English in Settings → Auto Memory → UI language
50
+ - Sidebar 「Memory」entry floating panel (Overview / Logs / Notes / Reflections / Connect / Calendar / Search)
51
+ - Settings page (Settings Auto Memory): storage paths, injection budget, reflection style, **UI language (中文 / English)**
25
52
  - **External memory inheritance**: import memories accumulated by other AI tools (CodeBuddy / Claude Code / Codex / project convention files)
26
53
 
27
54
  ---
@@ -86,7 +113,7 @@ Adjustable in the GUI (Settings → Auto Memory), including the UI language (zh
86
113
  ## Structure
87
114
 
88
115
  - `lib/index.js` — Host half: engine, injection, tools, routes (zero runtime deps, Node built-ins only)
89
- - `lib/client.js` — Browser half: memory panel + settings page (built-in zh/en i18n)
116
+ - `lib/client.js` — Browser half: memory panel (with calendar view) + settings page (built-in zh/en i18n)
90
117
  - `cordis.patch.yml` — Plugin row (`auto-memory`)
91
118
 
92
119
  ---
package/README.zh-CN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-auto-memory — DSH 自动记忆插件
2
2
 
3
- DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、可视化面板与设置页,支持继承其他 AI 工具的历史记忆。
3
+ DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日日志)自动注入与检索、每日反思、智能时段问候、日历视图与设置页,支持继承其他 AI 工具的历史记忆。
4
4
 
5
5
  [English](README.md) | [中文版](README.zh-CN.md)
6
6
 
@@ -8,18 +8,45 @@ DSH Web GUI 的记忆插件:三层记忆(用户级 / 项目笔记 / 每日
8
8
 
9
9
  ## 功能
10
10
 
11
+ ### 三层记忆
12
+
11
13
  | 层 | 位置 | 说明 |
12
14
  |---|---|---|
13
15
  | 用户级记忆 | `~/.dsh/memory/MEMORY.md` | 跨项目规则/偏好(用户明确要求时写) |
14
16
  | 项目笔记 | `{工作区}/.dsh-memory/MEMORY.md` | 项目长期约定、决策、架构要点 |
15
17
  | 每日日志 | `{工作区}/.dsh-memory/YYYY-MM-DD.md` | append-only 工作日志 |
16
- | 反思 | `{工作区}/.dsh-memory/reflections/YYYY-MM-DD.md` | 每日反思(自动提示生成) |
18
+ | 反思 | `{工作区}/.dsh-memory/reflections/YYYY-MM-DD.md` | 每日反思(后台结构化积累) |
19
+
20
+ - **自动注入**:每次组装系统提示词时注入 `<memory_system>` 块(用户规则 + 项目笔记 + 最近反思 + 最近 N 天日志尾部 + 未完成日历事项 + 写入纪律)
21
+ - **记忆操作可见**:更新/检索记忆时,AI 会在对话正文中明文说明(如"已把 X 记入今日日志""我查了记忆,发现…"),不藏在工具调用里
22
+
23
+ ### 智能时段问候(概览页)
24
+
25
+ 打开记忆面板第一眼看到的是生活化的时段问候,而不是严肃的技术信息:
26
+
27
+ - **一天多次**:早晨显示"昨天摘要 + 今天早晨"、上午显示上午的工作、中午/下午/晚上依次类推
28
+ - **智能时机**:离开超过 1 小时(下班/暂离)再打开,自动显示"欢迎回来"并列出期间的完成事项
29
+ - **抽屉式展开**:外层保持生活化问候风格;每个时段做成抽屉,点开才显示专业的工作明细
30
+ - **每日反思**:后台保留结构化反思(成果/教训/要点),前台只有轻松问候
31
+
32
+ ### 日历视图(四象限)
33
+
34
+ 新增「日历」页签(液态玻璃风格月视图):
35
+
36
+ - 月视图网格,今日高亮,点击任意日期添加事项
37
+ - **四象限色标**:重要紧急(红)/ 重要不紧急(蓝)/ 紧急不重要(橙)/ 不重要不紧急(灰)
38
+ - 点条目切换完成状态,再点删除;图例 + 星期头
39
+ - **跨对话持久**:数据存用户级 `~/.dsh/memory/CALENDAR.md`,所有工作区共享,重装 DSH 不丢
40
+ - **AI 主动维护**:AI 会从对话中提取 deadline、约定时间等自动写入日历(`calendar_add` / `calendar_list` / `calendar_done` / `calendar_remove`),并在正文转述;未完成事项注入每次会话的系统提示词
41
+
42
+ ### Agent 工具
43
+
44
+ `memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect` / `calendar_add` / `calendar_list` / `calendar_done` / `calendar_remove`
45
+
46
+ ### 界面
17
47
 
18
- - **自动注入**:每次组装系统提示词时注入 `<memory_system>` 块(用户规则 + 项目笔记 + 最近反思 + 最近 N 天日志尾部 + 写入纪律)
19
- - **每日反思**:检测到"昨天有日志但未生成反思"时,会话首轮自动请求 agent 生成昨日反思
20
- - **Agent 工具**:`memory_log` / `memory_note` / `memory_user` / `memory_recall` / `memory_external` / `memory_maintain` / `memory_status` / `memory_reflect`
21
- - **GUI**:侧边栏「记忆」入口 → 浮层面板(概览/日志/笔记/反思/接续/检索);设置页(设置 → 自动记忆)
22
- - **界面语言**:中文 / English 可切换(设置 → 自动记忆 → 界面语言)
48
+ - 侧边栏「记忆」入口 浮层面板(概览/日志/笔记/反思/接续/日历/检索)
49
+ - 设置页(设置 自动记忆):存储位置、注入预算、反思风格、**界面语言(中文 / English)**
23
50
  - **外部记忆继承**:接入其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件)积累的记忆
24
51
 
25
52
  ---
@@ -84,7 +111,7 @@ pnpm add @a9i5k4/dsh-auto-memory
84
111
  ## 结构
85
112
 
86
113
  - `lib/index.js` — Host 半:引擎、注入、工具、路由(零运行时依赖,仅 node 内置模块)
87
- - `lib/client.js` — 浏览器半:记忆面板 + 设置页(内置中英双语)
114
+ - `lib/client.js` — 浏览器半:记忆面板(含日历视图)+ 设置页(内置中英双语)
88
115
  - `cordis.patch.yml` — 插件行(`auto-memory`)
89
116
 
90
117
  ---
package/lib/client.js CHANGED
@@ -148,6 +148,9 @@ window.__ModuleLoader__.load({
148
148
  segMorning: '早晨', segForenoon: '上午', segNoon: '中午', segAfternoon: '下午', segEvening: '晚上',
149
149
  segPrefix: '今日', segMorningHint: '(昨日摘要)',
150
150
  welcomeBack: '欢迎回来!这段时间你完成了这些工作:',
151
+ yesterdayDrawer: '昨天',
152
+ greetMorning: '新的一天开始啦,先看看昨天做了什么。', greetForenoon: '上午好,今天也在稳步推进。', greetNoon: '中午好,歇口气再继续。', greetAfternoon: '下午好,下午也要元气满满。', greetEvening: '晚上好,辛苦一天了。',
153
+ greetSummary: '今天已经完成了 ', greetThings: ' 件工作,点开看看:',
151
154
  qUrgentImportant: '重要紧急', qImportant: '重要不紧急', qUrgent: '紧急不重要', qNone: '不重要不紧急', qUncategorized: '未分类',
152
155
  },
153
156
  en: {
@@ -206,6 +209,9 @@ window.__ModuleLoader__.load({
206
209
  segMorning: 'Morning', segForenoon: 'Forenoon', segNoon: 'Noon', segAfternoon: 'Afternoon', segEvening: 'Evening',
207
210
  segPrefix: 'Today ', segMorningHint: ' (yesterday summary)',
208
211
  welcomeBack: 'Welcome back! While you were away, you finished:',
212
+ yesterdayDrawer: 'Yesterday',
213
+ greetMorning: 'A fresh day! Let us look back at yesterday first.', greetForenoon: 'Good morning, steady progress today.', greetNoon: 'Good noon, take a break and keep going.', greetAfternoon: 'Good afternoon, keep up the good energy.', greetEvening: 'Good evening, well done today.',
214
+ greetSummary: 'You have completed ', greetThings: ' things today. Tap to expand:',
209
215
  qUrgentImportant: 'Urgent & Important', qImportant: 'Important', qUrgent: 'Urgent', qNone: 'Neither', qUncategorized: 'Uncategorized',
210
216
  }
211
217
  }
@@ -227,6 +233,7 @@ window.__ModuleLoader__.load({
227
233
  note: '/api/dsh-auto-memory/note',
228
234
  external: '/api/dsh-auto-memory/external',
229
235
  externalImport: '/api/dsh-auto-memory/external-import',
236
+ summarize: '/api/dsh-auto-memory/summarize',
230
237
  }
231
238
  function query(params) {
232
239
  var search = new URLSearchParams()
@@ -350,59 +357,87 @@ window.__ModuleLoader__.load({
350
357
  var g = props.greeting
351
358
  var ps = props.periodSummary
352
359
  if (!g) return null
360
+ var openPair = useState({})
361
+ var openMap = openPair[0]
362
+ var setOpenMap = openPair[1]
353
363
  // 智能时段判定
354
364
  var hour = new Date().getHours()
355
365
  var seg = hour < 9 ? 'morning' : hour < 12 ? 'forenoon' : hour < 14 ? 'noon' : hour < 18 ? 'afternoon' : 'evening'
356
366
  var segLabel = { morning: t('segMorning'), forenoon: t('segForenoon'), noon: t('segNoon'), afternoon: t('segAfternoon'), evening: t('segEvening') }[seg]
357
- var title = (g.period || '') + ' · ' + segLabel
367
+ // 收集抽屉: {key, label, hint, items, withTime}
368
+ var drawers = []
369
+ var curSegName = { morning: '早晨', forenoon: '上午', noon: '中午', afternoon: '下午', evening: '晚上' }[seg]
370
+ // 昨天抽屉(早晨显示)
371
+ if (seg === 'morning' && g.entries.length) {
372
+ drawers.push({ key: 'yesterday', label: t('yesterdayDrawer') + (g.yesterdayDate || ''), hint: '', items: g.entries, withTime: true, defaultOpen: true })
373
+ }
374
+ // 今天各时段抽屉(已过的时段)
375
+ var seenSeg = { '早晨': seg !== 'morning', '上午': (seg === 'forenoon' || seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '中午': (seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '下午': (seg === 'afternoon' || seg === 'evening'), '晚上': seg === 'evening' }
376
+ var segOrder = ['早晨', '上午', '中午', '下午', '晚上']
377
+ for (var si = 0; si < segOrder.length; si++) {
378
+ var sname = segOrder[si]
379
+ if (!seenSeg[sname]) continue
380
+ var items = (ps && ps.groups && ps.groups[sname]) || []
381
+ if (!items.length) continue
382
+ drawers.push({ key: 'seg-' + sname, label: t('segPrefix') + sname, hint: '', items: items.map(function (x) { return { time: '', text: x } }), withTime: false, defaultOpen: sname === curSegName })
383
+ }
384
+ // 外层:生活化问候
385
+ var totalCount = 0
386
+ for (var d0 = 0; d0 < drawers.length; d0++) totalCount += drawers[d0].items.length
387
+ var greetLine = g.greeting && String(g.greeting).trim()
388
+ ? String(g.greeting).trim()
389
+ : (function () {
390
+ if (away()) return t('welcomeBack')
391
+ var base = { morning: t('greetMorning'), forenoon: t('greetForenoon'), noon: t('greetNoon'), afternoon: t('greetAfternoon'), evening: t('greetEvening') }[seg]
392
+ if (totalCount > 0) base += t('greetSummary') + totalCount + t('greetThings')
393
+ return base
394
+ })()
395
+ // 离开>1小时后回来
396
+ function away() {
397
+ var lastSeen = 0
398
+ try { lastSeen = Number(localStorage.getItem('dsh-auto-memory.lastActive') || 0) } catch (e) {}
399
+ return lastSeen > 0 && (Date.now() - lastSeen) > 3600000
400
+ }
358
401
  var rows = []
359
- // 离开>1小时后回来 欢迎回来提示
360
- var lastSeen = 0
361
- try { lastSeen = Number(localStorage.getItem('dsh-auto-memory.lastActive') || 0) } catch (e) {}
362
- var now = Date.now()
363
- var away = lastSeen > 0 && (now - lastSeen) > 3600000
364
- if (away) {
365
- rows.push(h('div', { 'data-dam-content': '' }, t('welcomeBack')))
366
- }
367
- // 时段摘要(今天该时段之前的工作)
368
- if (ps && ps.entries && ps.entries.length) {
369
- var seen = { '凌晨': true, '早晨': seg === 'morning' ? false : true, '上午': (seg === 'forenoon' || seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '中午': (seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '下午': (seg === 'afternoon' || seg === 'evening'), '晚上': seg === 'evening' }
370
- var segRows = []
371
- var segOrder = ['早晨', '上午', '中午', '下午', '晚上']
372
- for (var si = 0; si < segOrder.length; si++) {
373
- var sname = segOrder[si]
374
- if (!seen[sname]) continue
375
- var items = (ps.groups && ps.groups[sname]) || []
376
- if (!items.length) continue
377
- if (segRows.length) segRows.push(h('div', { 'data-dam-hint': '' }, ''))
378
- segRows.push(h('div', { 'data-dam-hint': '' }, t('segPrefix') + sname + (sname === '早晨' ? t('segMorningHint') : '') + ':'))
379
- for (var ii = 0; ii < items.length; ii++) {
380
- (function (txt) {
381
- segRows.push(h('div', { 'data-dam-row': '', style: { marginBottom: '3px', alignItems: 'flex-start' } },
382
- h('span', { 'data-dam-muted': '', style: { flex: '0 0 10px', marginTop: '2px' } }, '·'),
383
- h('span', { style: { flex: '1', wordBreak: 'break-word' } }, txt)))
384
- })(items[ii])
385
- }
386
- }
387
- if (segRows.length) rows.push(h('div', null, segRows))
402
+ rows.push(h('div', { 'data-dam-content': '' }, greetLine))
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 () {})
388
412
  }
389
- // 昨天时间轴(早晨时段显示昨天摘要)
390
- if (seg === 'morning' && g.entries.length) {
391
- rows.push(h('div', { 'data-dam-hint': '' }, t('yesterdayTimeline') + (g.yesterdayDate || '') + ')'))
392
- for (var i = 0; i < g.entries.length; i++) {
393
- (function (en) {
394
- rows.push(h('div', { 'data-dam-row': '', style: { marginBottom: '4px', alignItems: 'flex-start' } },
395
- h('span', { 'data-dam-muted': '', style: { flex: '0 0 44px', fontFamily: 'monospace', fontSize: '11.5px', marginTop: '2px' } }, en.time),
396
- h('span', { style: { flex: '1', wordBreak: 'break-word' } }, en.text)))
397
- })(g.entries[i])
398
- }
413
+ for (var d = 0; d < drawers.length; d++) {
414
+ (function (dr) {
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
+ }
419
+ rows.push(h('div', { key: dr.key, style: { marginTop: '8px' } },
420
+ h('button', {
421
+ 'data-dam-btn': '',
422
+ onClick: function () { var n = Object.assign({}, openMap); n[dr.key] = !isOpen; setOpenMap(n) },
423
+ style: { width: '100%', display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '5px 8px', borderRadius: '7px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 50%, transparent)' },
424
+ },
425
+ h('span', { style: { fontWeight: 600, fontSize: '12.5px' } }, (isOpen ? '▼ ' : '▶ ') + dr.label),
426
+ h('span', { 'data-dam-muted': '', style: { fontSize: '11.5px' } }, dr.items.length + t('logEntries'))),
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,
429
+ dr.items.map(function (en) {
430
+ return h('div', { key: (en.time || '') + en.text, style: { marginBottom: '3px', fontSize: '12.5px', lineHeight: 1.5 } },
431
+ dr.withTime && en.time ? h('span', { 'data-dam-muted': '', style: { fontFamily: 'monospace', fontSize: '11px', marginRight: '6px' } }, en.time) : null,
432
+ h('span', { style: { wordBreak: 'break-word' } }, en.text))
433
+ })) : null))
434
+ })(drawers[d])
399
435
  }
400
436
  // 待反思提醒
401
437
  if (g.pendingReflectionDate) {
402
- rows.push(h('div', { 'data-dam-hint': '' }, t('pendingReflectionShort') + g.pendingReflectionDate))
438
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '8px' } }, t('pendingReflectionShort') + g.pendingReflectionDate))
403
439
  }
404
- if (!rows.length) return null
405
- return h(Card, { title: title }, rows)
440
+ return h(Card, { title: (g.period || '') + ' · ' + segLabel }, rows)
406
441
  }
407
442
 
408
443
  function OverviewTab() {
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.6",
4
+ "version": "0.1.8",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {