@a9i5k4/dsh-auto-memory 0.1.17 → 0.1.18

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/cordis.patch.yml CHANGED
@@ -1,9 +1,9 @@
1
- # dsh-auto-memory bundle patch: inserts the dual-face plugin row into the web
2
- # profile roster. Applied as a profile bundle layer (the `dsh.bundle.patch`
3
- # manifest field). The node half (exports ".") runs in the host process
4
- # (memory engine, /api/dsh-auto-memory routes, agent tools, prompt injection);
5
- # the `dsh.client` declaration in package.json makes the browser half
6
- # (exports "./client", served at /plugins/<id>/client.js) load in the web GUI.
7
- - insert:
8
- - id: auto-memory
9
- name: '@a9i5k4/dsh-auto-memory'
1
+ # dsh-auto-memory bundle patch: inserts the dual-face plugin row into the web
2
+ # profile roster. Applied as a profile bundle layer (the `dsh.bundle.patch`
3
+ # manifest field). The node half (exports ".") runs in the host process
4
+ # (memory engine, /api/dsh-auto-memory routes, agent tools, prompt injection);
5
+ # the `dsh.client` declaration in package.json makes the browser half
6
+ # (exports "./client", served at /plugins/<id>/client.js) load in the web GUI.
7
+ - insert:
8
+ - id: auto-memory-dev
9
+ name: '@deepseek-ai/dsh-auto-memory'
package/lib/client.js CHANGED
@@ -115,7 +115,7 @@ window.__ModuleLoader__.load({
115
115
  kvAvail: '可用', kvUnavail: '不可用', kvDup: '项目笔记重复标题', kvDupCount: ' 个',
116
116
  kvMem: '记忆文件', kvMemUser: '用户', kvMemNotes: '笔记', kvMemLog: '日志', kvMemMissing: '缺失',
117
117
  kvWs: '当前工作区', kvWsUnknown: '(未取到)', kvApi: 'API 连通', unknown: '(未知)',
118
- guideTitle: '欢迎使用 dsh-auto-memory', guideSub: '这个插件能干什么:',
118
+ guideTitle: '欢迎使用 dsh-auto-memory(开发版)', guideSub: '这个插件能干什么:',
119
119
  gFeat1: '三层记忆自动注入与检索:用户级 / 项目笔记 / 每日日志,每轮对话自动读、自动写',
120
120
  gFeat2: '每轮对话结束自动沉淀:修了什么 bug、做了哪些决策,自动按主题写进今日日志',
121
121
  gFeat3: 'AI 时段问候与三级抽屉:打开「记忆」面板,AI 按时段向你汇报当天进展',
@@ -205,7 +205,7 @@ window.__ModuleLoader__.load({
205
205
  kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
206
206
  kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
207
207
  kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
208
- guideTitle: 'Welcome to dsh-auto-memory', guideSub: 'What this plugin does:',
208
+ guideTitle: 'Welcome to dsh-auto-memory (dev)', guideSub: 'What this plugin does:',
209
209
  gFeat1: 'Three-layer memory with automatic injection & retrieval: user / project notes / daily logs',
210
210
  gFeat2: 'Auto-consolidation after every turn: bugs fixed and decisions made are written into today\'s log automatically',
211
211
  gFeat3: 'AI period greetings with three-level drawers: open the Memory panel for a period-by-period digest',
@@ -313,6 +313,21 @@ window.__ModuleLoader__.load({
313
313
 
314
314
  // ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
315
315
  var CHANGELOG = {
316
+ '0.1.18': { zh: [
317
+ '稳定版:整合 0.1.13~0.1.17 全部修复与优化',
318
+ '核心:上下文缓存策略重写——记忆注入迁至运行时上下文快照,系统提示词保持稳定,DeepSeek 前缀缓存全程命中,不再白白消耗 token(命中率恢复 95%+)',
319
+ '修复:秒级时间戳击穿前缀缓存(改日期级)',
320
+ '修复:npm 安装后客户端面板无法加载(bundle 注册名)',
321
+ '新增:更新说明弹窗/首次安装指导/暂离回归自动打开记忆窗口/动态通知中心',
322
+ '提示:pnpm v11 默认限制安装发布不足 1 天的版本,想立即获取新版请在 pnpm-workspace.yaml 设 minimumReleaseAge: 0 或使用显式版本号',
323
+ ], en: [
324
+ 'Stable release: consolidates 0.1.13~0.1.17 fixes and polish',
325
+ 'Core: context-cache strategy rewrite — memory injection moved to runtime-context snapshot, system prompt stays byte-stable, DeepSeek prefix cache hits throughout, no more wasted tokens (hit rate back to 95%+)',
326
+ 'Fix: second-level timestamp breaking prefix cache (now date-level)',
327
+ 'Fix: client panel not loading after npm install (bundle registration id)',
328
+ 'New: update dialog / first-run guide / memory panel auto-opens after >1h away / dynamic notice center',
329
+ 'Note: pnpm v11 blocks packages published <1 day ago by default; set minimumReleaseAge: 0 in pnpm-workspace.yaml or use an explicit version for the latest immediately',
330
+ ] },
316
331
  '0.1.17': { zh: [
317
332
  '新增:动态通知中心——插件自动拉取发布者的重要提醒(重大 bug/升级建议),特定时间窗口内显示,无需等待发版即可收到推送',
318
333
  '优化:0.1.14 及以下旧版本已全部弃用(缓存击穿浪费 token),升级到 0.1.16+ 彻底解决',
@@ -0,0 +1,568 @@
1
+ /* dsh-auto-memory — browser half (hand-written __ModuleLoader__ bundle).
2
+ * Registers three additive surfaces:
3
+ * 1. sidebar.footer.action — 「记忆」入口按钮(开关左下角浮层面板)
4
+ * 2. shell.overlay — 记忆面板:概览 / 日志 / 笔记 / 反思 / 检索
5
+ * 液态玻璃视觉(backdrop-filter + --dsw-alias-* 主题令牌),
6
+ * 可拖动 / 右下角缩放 / 开关缩放动画 / 位置大小持久化。
7
+ * 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
8
+ * Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
9
+ */
10
+ window.__ModuleLoader__.load({
11
+ id: '@deepseek-ai/dsh-auto-memory',
12
+ factory: (require) => {
13
+ var module = { exports: {} }
14
+ var exports = module.exports
15
+ var React = require('react')
16
+ var h = React.createElement
17
+ var useState = React.useState
18
+ var useEffect = React.useEffect
19
+ var useReducer = React.useReducer
20
+
21
+ // ───────────────────────── 控制器 ─────────────────────────
22
+ var listeners = new Set()
23
+ var panelOpen = false
24
+ var panelClosing = false
25
+ var closeTimer = null
26
+ // 几何状态:left/top/width/height,持久化到 localStorage(用户可拖动/缩放)
27
+ var GEOM_KEY = 'dsh-auto-memory.panel.geom'
28
+ var DEFAULT_W = 440
29
+ var DEFAULT_H = 560
30
+ var DEFAULT_GAP = 16
31
+ function defaultGeom() {
32
+ var vh = window.innerHeight || 800
33
+ return {
34
+ left: DEFAULT_GAP,
35
+ top: Math.max(DEFAULT_GAP, vh - DEFAULT_H - DEFAULT_GAP),
36
+ width: DEFAULT_W,
37
+ height: DEFAULT_H,
38
+ }
39
+ }
40
+ var geom = null
41
+ function clampGeom(g) {
42
+ var vw = window.innerWidth || 1280
43
+ var vh = window.innerHeight || 800
44
+ var w = Math.max(300, Math.min(g.width || DEFAULT_W, vw - DEFAULT_GAP * 2))
45
+ var h = Math.max(240, Math.min(g.height || DEFAULT_H, vh - DEFAULT_GAP * 2))
46
+ return {
47
+ left: Math.max(DEFAULT_GAP, Math.min(g.left !== undefined ? g.left : DEFAULT_GAP, vw - w - DEFAULT_GAP)),
48
+ top: Math.max(DEFAULT_GAP, Math.min(g.top !== undefined ? g.top : DEFAULT_GAP, vh - h - DEFAULT_GAP)),
49
+ width: w,
50
+ height: h,
51
+ }
52
+ }
53
+ function loadGeom() {
54
+ try {
55
+ var raw = localStorage.getItem(GEOM_KEY)
56
+ if (raw) return clampGeom(JSON.parse(raw))
57
+ } catch (e) {}
58
+ return clampGeom(defaultGeom())
59
+ }
60
+ function persistGeom() { if (geom) { try { localStorage.setItem(GEOM_KEY, JSON.stringify(geom)) } catch (e) {} } }
61
+ function emit() { listeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
62
+ var controller = {
63
+ isOpen: function () { return panelOpen },
64
+ isClosing: function () { return panelClosing },
65
+ geom: function () { if (!geom) geom = loadGeom(); return geom },
66
+ setGeom: function (partial) {
67
+ var cur = controller.geom()
68
+ geom = clampGeom({
69
+ left: partial.left !== undefined ? partial.left : cur.left,
70
+ top: partial.top !== undefined ? partial.top : cur.top,
71
+ width: partial.width !== undefined ? partial.width : cur.width,
72
+ height: partial.height !== undefined ? partial.height : cur.height,
73
+ })
74
+ emit()
75
+ },
76
+ flushGeom: function () { persistGeom() },
77
+ resetGeom: function () { geom = clampGeom(defaultGeom()); persistGeom(); emit() },
78
+ toggle: function () { if (panelOpen) controller.close(); else controller.open() },
79
+ open: function () {
80
+ if (closeTimer) { clearTimeout(closeTimer); closeTimer = null }
81
+ panelOpen = true; panelClosing = false; emit()
82
+ },
83
+ close: function () {
84
+ if (!panelOpen || panelClosing) return
85
+ panelClosing = true; emit()
86
+ closeTimer = setTimeout(function () {
87
+ panelOpen = false; panelClosing = false; closeTimer = null; emit()
88
+ }, 170)
89
+ },
90
+ subscribe: function (fn) { listeners.add(fn); return function () { listeners.delete(fn) } },
91
+ }
92
+
93
+ // ───────────────────────── API ─────────────────────────
94
+ var API = {
95
+ state: '/api/dsh-auto-memory/state',
96
+ list: '/api/dsh-auto-memory/list',
97
+ file: '/api/dsh-auto-memory/file',
98
+ recall: '/api/dsh-auto-memory/recall',
99
+ config: '/api/dsh-auto-memory/config',
100
+ reflect: '/api/dsh-auto-memory/reflect',
101
+ note: '/api/dsh-auto-memory/note',
102
+ external: '/api/dsh-auto-memory/external',
103
+ externalImport: '/api/dsh-auto-memory/external-import',
104
+ }
105
+ function query(params) {
106
+ var search = new URLSearchParams()
107
+ for (var key in params) if (params[key] !== undefined && params[key] !== '') search.set(key, String(params[key]))
108
+ var text = search.toString()
109
+ return text ? '?' + text : ''
110
+ }
111
+ async function apiGet(path, params) {
112
+ var res = await fetch(path + query(params))
113
+ var body = await res.json().catch(function () { return {} })
114
+ if (!res.ok) throw new Error(body.error || ('HTTP ' + res.status))
115
+ return body
116
+ }
117
+ async function apiPost(path, payload) {
118
+ var res = await fetch(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
119
+ var body = await res.json().catch(function () { return {} })
120
+ if (!res.ok) throw new Error(body.error || ('HTTP ' + res.status))
121
+ return body
122
+ }
123
+
124
+ // ───────────────────────── 样式 ─────────────────────────
125
+ // 视觉:液态玻璃(毛玻璃)—— backdrop-filter + DSH 主题令牌(--dsw-alias-*),
126
+ // 跟随亮/暗主题与页面背景自适应;位置/尺寸由 JS 几何状态驱动(可拖动、可缩放)。
127
+ var CSS = [
128
+ '[data-dam-panel] { position: fixed; left: 16px; width: 440px; height: 560px;',
129
+ ' max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);',
130
+ ' display: flex; flex-direction: column; overflow: hidden; z-index: 3000; pointer-events: auto;',
131
+ ' border-radius: 16px; font: 13px/1.55 system-ui, "Segoe UI", sans-serif;',
132
+ ' background: color-mix(in srgb, var(--dsw-alias-bg-overlay, rgba(255,255,255,.86)) 58%, transparent);',
133
+ ' -webkit-backdrop-filter: blur(28px) saturate(1.55); backdrop-filter: blur(28px) saturate(1.55);',
134
+ ' border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 65%, transparent);',
135
+ ' box-shadow: 0 24px 64px rgba(0,0,0,.22), 0 4px 16px rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.22);',
136
+ ' color: var(--dsw-alias-label-primary, #1f2328);',
137
+ ' animation: dam-in .2s cubic-bezier(.2,.9,.3,1.15) both; transform-origin: left bottom; }',
138
+ '@keyframes dam-in { from { opacity: 0; transform: scale(.92) translateY(10px); } to { opacity: 1; transform: none; } }',
139
+ '@keyframes dam-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.95) translateY(6px); } }',
140
+ '[data-dam-panel][data-closing="true"] { animation: dam-out .16s ease-in both; }',
141
+ '[data-dam-panel]::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 64%; pointer-events: none;',
142
+ ' background: linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,0) 70%); border-radius: 16px 16px 0 0; }',
143
+ '[data-dam-panel][data-dragging="true"] { user-select: none; }',
144
+ '[data-dam-panel] header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; cursor: grab;',
145
+ ' border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
146
+ '[data-dam-panel][data-dragging="true"] header { cursor: grabbing; }',
147
+ '[data-dam-panel] header strong { font-size: 14px; }',
148
+ '[data-dam-panel] header .dam-spacer { flex: 1; }',
149
+ '[data-dam-resize] { position: absolute; right: 0; bottom: 0; width: 22px; height: 22px; cursor: nwse-resize;',
150
+ ' opacity: .55; z-index: 2; border-radius: 0 0 16px 0;',
151
+ ' background: linear-gradient(135deg, transparent 50%, color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 45%, transparent) 50%); }',
152
+ '[data-dam-resize]:hover { opacity: 1; }',
153
+ '[data-dam-btn] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .75; font-size: 13px; padding: 4px 8px; border-radius: 6px; }',
154
+ '[data-dam-btn]:hover { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 16%, transparent); }',
155
+ '[data-dam-tabs] { display: flex; gap: 2px; padding: 6px 10px; border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.2)) 55%, transparent); }',
156
+ '[data-dam-tab] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .6; padding: 5px 10px; border-radius: 7px; font-size: 12.5px; }',
157
+ '[data-dam-tab][data-active="true"] { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 18%, transparent); font-weight: 600; }',
158
+ '[data-dam-body] { flex: 1; overflow: auto; padding: 12px 14px; }',
159
+ '[data-dam-kv] { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 12.5px; }',
160
+ '[data-dam-kv] b { opacity: .55; font-weight: 500; }',
161
+ '[data-dam-card] { border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.22)) 60%, transparent); border-radius: 9px; padding: 8px 10px; margin-bottom: 8px; font-size: 12.5px;',
162
+ ' background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent); }',
163
+ '[data-dam-card] .dam-date { font-weight: 600; margin-bottom: 4px; }',
164
+ '[data-dam-card] .dam-content { white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }',
165
+ '[data-dam-banner] { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 55%, transparent);',
166
+ ' background: color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 13%, transparent); border-radius: 9px; padding: 8px 10px; margin-bottom: 10px; font-size: 12.5px; }',
167
+ '[data-dam-input], [data-dam-select] { width: 100%; box-sizing: border-box; background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent); color: inherit; border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 60%, transparent); border-radius: 7px; padding: 6px 8px; font: inherit; }',
168
+ '[data-dam-row] { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }',
169
+ '[data-dam-row] label { flex: 0 0 110px; opacity: .8; font-size: 12.5px; }',
170
+ '[data-dam-hint] { opacity: .5; font-size: 11.5px; margin-top: 2px; }',
171
+ '[data-dam-sidebar-btn] { display: flex; align-items: center; gap: 6px; width: 100%; border: none; background: transparent; color: inherit; cursor: pointer; padding: 6px 10px; border-radius: 8px; font: inherit; font-size: 13px; opacity: .8; }',
172
+ '[data-dam-sidebar-btn]:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 14%, transparent); opacity: 1; }',
173
+ '[data-dam-sidebar-btn][data-active="true"] { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 16%, transparent); color: var(--dsw-alias-brand-primary, #4f7cff); }',
174
+ '[data-dam-error] { color: var(--dsw-alias-state-error-primary, #d64545); font-size: 12px; margin-top: 6px; white-space: pre-wrap; }',
175
+ '[data-dam-muted] { opacity: .55; }',
176
+ '@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {',
177
+ ' [data-dam-panel] { background: var(--dsw-alias-bg-overlay, #ffffff); } }',
178
+ ].join('\n')
179
+ var STYLE_ID = 'dsh-auto-memory-css'
180
+ function ensureStyle() {
181
+ if (document.getElementById(STYLE_ID)) return
182
+ var tag = document.createElement('style')
183
+ tag.id = STYLE_ID
184
+ tag.dataset.plugin = '@deepseek-ai/dsh-auto-memory'
185
+ tag.textContent = CSS
186
+ document.head.appendChild(tag)
187
+ }
188
+
189
+ // ───────────────────────── 通用小组件 ─────────────────────────
190
+ function useTick() { return useReducer(function (x) { return x + 1 }, 0) }
191
+
192
+ function Banner(props) {
193
+ return h('div', { 'data-dam-banner': '' }, props.children)
194
+ }
195
+
196
+ function Card(props) {
197
+ return h('div', { 'data-dam-card': '' },
198
+ h('div', { className: 'dam-date' }, props.title),
199
+ h('div', { className: 'dam-content' }, props.children))
200
+ }
201
+
202
+ function Loading() { return h('div', { 'data-dam-muted': '' }, '加载中…') }
203
+
204
+ // ───────────────────────── 侧边栏入口 ─────────────────────────
205
+ function SidebarButton() {
206
+ var tick = useTick()
207
+ useEffect(function () { return controller.subscribe(tick[1]) }, [])
208
+ return h('button', {
209
+ 'data-dam-sidebar-btn': '',
210
+ title: '记忆面板',
211
+ 'data-active': (panelOpen || panelClosing) ? 'true' : undefined,
212
+ onClick: function () { controller.toggle() },
213
+ }, h('span', null, '记忆'))
214
+ }
215
+
216
+ // ───────────────────────── 记忆面板 ─────────────────────────
217
+ function fmtSize(n) {
218
+ if (n >= 1024 * 1024) return (n / 1024 / 1024).toFixed(1) + ' MB'
219
+ if (n >= 1024) return (n / 1024).toFixed(1) + ' KB'
220
+ return n + ' B'
221
+ }
222
+
223
+ function OverviewTab() {
224
+ var tick = useTick()
225
+ var state = null
226
+ useEffect(function () {
227
+ var alive = true
228
+ apiGet(API.state).then(function (s) { if (alive) { state = s; tick[1]() } }).catch(function () {})
229
+ return function () { alive = false }
230
+ }, [])
231
+ if (!state) return h(Loading)
232
+ return h('div', null,
233
+ state.pendingReflection
234
+ ? h(Banner, null, '待生成反思: ' + state.pendingReflection + ' —— 打开/继续会话,agent 会在会话开始时主动呈现昨日反思。')
235
+ : null,
236
+ h('div', { 'data-dam-kv': '' },
237
+ h('b', null, '工作区'), h('span', null, state.ws),
238
+ h('b', null, '用户级记忆'), h('span', null, state.userFile + ' (' + fmtSize(state.sizes.user) + ')'),
239
+ h('b', null, '项目笔记'), h('span', null, state.notesPath + ' (' + fmtSize(state.sizes.notes) + ')'),
240
+ h('b', null, '今日日志'), h('span', null, state.logPath + ' · ' + state.todayEntries + ' 条 (' + fmtSize(state.sizes.log) + ')'),
241
+ h('b', null, '最近反思'), h('span', null, state.latestReflectionDate || '(无)'),
242
+ h('b', null, '配置文件'), h('span', null, state.configReadError ? ('读取失败: ' + state.configReadError) : '正常')),
243
+ h('div', { 'data-dam-hint': '' }, '上次刷新: ' + (state.refreshedAt ? new Date(state.refreshedAt).toLocaleString() : '尚未')))
244
+ }
245
+
246
+ function LogsTab() {
247
+ var tick = useTick()
248
+ var data = null
249
+ var open = null
250
+ var content = ''
251
+ useEffect(function () {
252
+ var alive = true
253
+ apiGet(API.list).then(function (d) { if (alive) { data = d; tick[1]() } }).catch(function () {})
254
+ return function () { alive = false }
255
+ }, [])
256
+ useEffect(function () {
257
+ if (!open) return
258
+ var alive = true
259
+ apiGet(API.file, { path: open }).then(function (d) { if (alive) { content = d.content; tick[1]() } }).catch(function () {})
260
+ return function () { alive = false }
261
+ }, [open])
262
+ if (!data) return h(Loading)
263
+ var rows = []
264
+ if (open) {
265
+ rows.push(h(Card, { title: pathName(open) },
266
+ h('div', { 'data-dam-content': '' }, content || '(空)'),
267
+ h('button', { 'data-dam-btn': '', onClick: function () { open = null; content = ''; tick[1]() } }, '← 返回')))
268
+ } else {
269
+ rows.push(h('div', { 'data-dam-hint': '' }, '点击日期查看当日日志(append-only):'))
270
+ for (var i = 0; i < data.logs.length; i++) {
271
+ (function (log) {
272
+ rows.push(h(Card, { title: log.date + ' · ' + fmtSize(log.size) },
273
+ h('button', { 'data-dam-btn': '', onClick: function () { open = log.date + '.md'; tick[1]() } }, '查看')))
274
+ })(data.logs[i])
275
+ }
276
+ }
277
+ return h('div', null, rows)
278
+ }
279
+ function pathName(p) { var parts = String(p).split(/[\\/]/); return parts[parts.length - 1] }
280
+
281
+ function NotesTab() {
282
+ var tick = useTick()
283
+ var data = null
284
+ var draft = ''
285
+ var saving = false
286
+ var msg = ''
287
+ var err = ''
288
+ if (!data) {
289
+ apiGet(API.state).then(function (s) { data = s; tick[1]() }).catch(function () {})
290
+ return h(Loading)
291
+ }
292
+ function save() {
293
+ if (!draft.trim() || saving) return
294
+ saving = true; msg = ''; err = ''
295
+ apiPost(API.note, { content: draft.trim() }).then(function (d) {
296
+ msg = d.result || '已追加 ✓'; draft = ''; saving = false; tick[1]()
297
+ }).catch(function (e) { err = e.message; saving = false; tick[1]() })
298
+ }
299
+ return h('div', null,
300
+ h('div', { 'data-dam-hint': '' }, '项目笔记: ' + data.notesPath),
301
+ h('textarea', { 'data-dam-input': '', rows: 6, placeholder: '想追加到项目笔记的内容…(保存时自动带日期标题)', value: draft, onChange: function (e) { draft = e.target.value; tick[1]() } }),
302
+ h('div', { 'data-dam-row': '' },
303
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: saving }, saving ? '保存中…' : '追加'),
304
+ h('span', { 'data-dam-hint': '' }, '建议直接用对话让 agent 调 memory_note;此处为手动追加。')),
305
+ msg ? h('div', null, msg) : null,
306
+ err ? h('div', { 'data-dam-error': '' }, err) : null)
307
+ }
308
+
309
+ function ReflectionsTab() {
310
+ var tick = useTick()
311
+ var data = null
312
+ var open = null
313
+ var content = ''
314
+ useEffect(function () {
315
+ var alive = true
316
+ apiGet(API.list).then(function (d) { if (alive) { data = d; tick[1]() } }).catch(function () {})
317
+ return function () { alive = false }
318
+ }, [])
319
+ useEffect(function () {
320
+ if (!open) return
321
+ var alive = true
322
+ apiGet(API.file, { path: open }).then(function (d) { if (alive) { content = d.content; tick[1]() } }).catch(function () {})
323
+ return function () { alive = false }
324
+ }, [open])
325
+ if (!data) return h(Loading)
326
+ var rows = []
327
+ if (open) {
328
+ rows.push(h(Card, { title: '反思 ' + pathName(open) }, h('div', { 'data-dam-content': '' }, content || '(空)')))
329
+ rows.push(h('button', { 'data-dam-btn': '', onClick: function () { open = null; content = ''; tick[1]() } }, '← 返回'))
330
+ } else {
331
+ if (!data.reflections.length) rows.push(h('div', { 'data-dam-muted': '' }, '还没有反思。每天第一次会话时,agent 会主动呈现前一天的工作反思。'))
332
+ for (var i = 0; i < data.reflections.length; i++) {
333
+ (function (r) {
334
+ rows.push(h(Card, { title: r.date + ' · ' + fmtSize(r.size) },
335
+ h('button', { 'data-dam-btn': '', onClick: function () { open = 'reflections/' + r.name; tick[1]() } }, '查看')))
336
+ })(data.reflections[i])
337
+ }
338
+ }
339
+ return h('div', null, rows)
340
+ }
341
+
342
+ function SearchTab() {
343
+ var tick = useTick()
344
+ var q = ''
345
+ var result = null
346
+ var busy = false
347
+ function search() {
348
+ if (!q.trim() || busy) return
349
+ busy = true; result = null; tick[1]()
350
+ apiPost(API.recall, { query: q.trim() }).then(function (d) { result = d.result; busy = false; tick[1]() })
351
+ .catch(function (e) { result = '检索失败: ' + e.message; busy = false; tick[1]() })
352
+ }
353
+ return h('div', null,
354
+ h('div', { 'data-dam-row': '' },
355
+ h('input', { 'data-dam-input': '', placeholder: '搜记忆:关键词或自包含描述…', value: q, onChange: function (e) { q = e.target.value; tick[1]() }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
356
+ h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, '检索')),
357
+ result ? h(Card, { title: '结果' }, h('div', { 'data-dam-content': '' }, result)) : null)
358
+ }
359
+
360
+ var TOOL_LABEL = { workbuddy: 'WorkBuddy', codebuddy: 'CodeBuddy', claude: 'Claude Code', codex: 'Codex', 'project-files': '项目文件' }
361
+ function ConnectTab() {
362
+ var tick = useTick()
363
+ var sources = null
364
+ var busy = {}
365
+ var msg = ''
366
+ var err = ''
367
+ function load() {
368
+ apiGet(API.external).then(function (d) { sources = d.sources || []; tick[1]() }).catch(function (e) { err = e.message; tick[1]() })
369
+ }
370
+ useEffect(function () { load(); var t = setInterval(load, 60000); return function () { clearInterval(t) } }, [])
371
+ if (!sources) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
372
+ function doImport(source, target) {
373
+ busy[source] = true; msg = ''; err = ''
374
+ apiPost(API.externalImport, { source: source, target: target }).then(function (d) {
375
+ msg = d.result || '接入完成'; busy[source] = false; tick[1]()
376
+ }).catch(function (e) { err = e.message; busy[source] = false; tick[1]() })
377
+ }
378
+ function importAll() {
379
+ var md = sources.filter(function (s) { return s.kind !== 'sessions' && s.enabled !== false })
380
+ msg = '正在接入 ' + md.length + ' 个源…'; err = ''
381
+ var chain = Promise.resolve()
382
+ md.forEach(function (s) { chain = chain.then(function () { return apiPost(API.externalImport, { source: s.id, target: 'project' }) }) })
383
+ chain.then(function () { msg = '全部接入完成 ✓'; tick[1]() }).catch(function (e) { err = e.message; tick[1]() })
384
+ }
385
+ var cards = []
386
+ if (!sources.length) {
387
+ cards.push(h('div', { 'data-dam-muted': '' }, '未检测到其他 AI 工具的记忆文件(WorkBuddy/CodeBuddy/Claude Code/Codex)。'))
388
+ }
389
+ for (var i = 0; i < sources.length; i++) {
390
+ (function (s) {
391
+ var actions = []
392
+ if (s.kind === 'sessions') {
393
+ actions.push(h('span', { 'data-dam-hint': '' }, '会话源:共 ' + s.fileCount + ' 个会话文件,用 memory_recall 按需检索'))
394
+ } else {
395
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'project') } }, busy[s.id] ? '接入中…' : '接入项目笔记'))
396
+ actions.push(h('button', { 'data-dam-btn': '', disabled: !!busy[s.id], onClick: function () { doImport(s.id, 'user') } }, '接入用户记忆'))
397
+ }
398
+ cards.push(h(Card, { title: s.name + ' · ' + s.tool + (s.enabled === false ? ' · 已停用' : '') },
399
+ h('div', { 'data-dam-hint': '' }, s.kind + ' · ' + s.fileCount + ' 个文件 · ' + fmtSize(s.size)),
400
+ s.preview ? h('div', { 'data-dam-content': '' }, s.preview) : null,
401
+ h('div', { 'data-dam-row': '' }, actions)))
402
+ })(sources[i])
403
+ }
404
+ return h('div', null,
405
+ h('div', { 'data-dam-hint': '' }, '把其他 AI 工具(WorkBuddy / CodeBuddy / Claude Code / Codex / 项目约定文件)积累的记忆接入当前 DSH 工作。接入后内容写入本地记忆并自动标注来源,后续会随会话自动注入。'),
406
+ h('div', { 'data-dam-row': '' },
407
+ h('button', { 'data-dam-btn': '', onClick: importAll }, '一键接入全部'),
408
+ h('button', { 'data-dam-btn': '', onClick: load }, '重新扫描')),
409
+ msg ? h('div', null, msg) : null,
410
+ err ? h('div', { 'data-dam-error': '' }, err) : null,
411
+ cards)
412
+ }
413
+
414
+ // ───────────────────────── 拖动/缩放辅助 ─────────────────────────
415
+ var dragActive = false
416
+ function startPointerDrag(onMove, interactiveSel) {
417
+ return function (e) {
418
+ // 命中交互控件(按钮/输入框等)时不启动拖动
419
+ if (interactiveSel && e.target && e.target.closest && e.target.closest(interactiveSel)) return
420
+ e.preventDefault()
421
+ e.stopPropagation()
422
+ var startX = e.clientX
423
+ var startY = e.clientY
424
+ var moved = false
425
+ function move(ev) {
426
+ var dx = ev.clientX - startX
427
+ var dy = ev.clientY - startY
428
+ if (!moved && Math.abs(dx) < 2 && Math.abs(dy) < 2) return
429
+ moved = true
430
+ if (!dragActive) { dragActive = true; emit() }
431
+ onMove(dx, dy)
432
+ }
433
+ function up() {
434
+ window.removeEventListener('pointermove', move)
435
+ window.removeEventListener('pointerup', up)
436
+ if (dragActive) { dragActive = false; emit() }
437
+ controller.flushGeom()
438
+ }
439
+ window.addEventListener('pointermove', move)
440
+ window.addEventListener('pointerup', up)
441
+ }
442
+ }
443
+
444
+ function MemoryPanel() {
445
+ var tick = useTick()
446
+ var tab = 'overview'
447
+ var g = controller.geom()
448
+ useEffect(function () { return controller.subscribe(tick[1]) }, [])
449
+ if (!panelOpen && !panelClosing) return null
450
+ var body
451
+ if (tab === 'overview') body = h(OverviewTab)
452
+ else if (tab === 'logs') body = h(LogsTab)
453
+ else if (tab === 'notes') body = h(NotesTab)
454
+ else if (tab === 'reflections') body = h(ReflectionsTab)
455
+ else if (tab === 'connect') body = h(ConnectTab)
456
+ else body = h(SearchTab)
457
+ var tabs = [['overview', '概览'], ['logs', '日志'], ['notes', '笔记'], ['reflections', '反思'], ['connect', '接续'], ['search', '检索']]
458
+ var style = {
459
+ left: g.left + 'px',
460
+ top: g.top + 'px',
461
+ width: g.width + 'px',
462
+ height: g.height + 'px',
463
+ }
464
+ var dragMove = startPointerDrag(function (dx, dy) {
465
+ controller.setGeom({ left: g.left + dx, top: g.top + dy })
466
+ }, '[data-dam-btn], [data-dam-tab], [data-dam-input], [data-dam-select], textarea')
467
+ var resizeMove = startPointerDrag(function (dx, dy) {
468
+ controller.setGeom({ width: g.width + dx, height: g.height + dy })
469
+ })
470
+ return h('div', {
471
+ 'data-dam-panel': '',
472
+ style: style,
473
+ 'data-closing': panelClosing ? 'true' : undefined,
474
+ 'data-dragging': dragActive ? 'true' : undefined,
475
+ },
476
+ h('header', {
477
+ title: '拖动移动',
478
+ onPointerDown: dragMove,
479
+ },
480
+ h('strong', null, '自动记忆'),
481
+ h('span', { className: 'dam-spacer' }),
482
+ h('button', { 'data-dam-btn': '', title: '恢复默认位置', onClick: function () { controller.resetGeom() } }, '⤾'),
483
+ h('button', { 'data-dam-btn': '', title: '刷新', onClick: function () { tick[1]() } }, '⟳'),
484
+ h('button', { 'data-dam-btn': '', title: '关闭', onClick: function () { controller.close() } }, '✕')),
485
+ h('div', { 'data-dam-tabs': '' }, tabs.map(function (t) {
486
+ return h('button', { key: t[0], 'data-dam-tab': '', 'data-active': tab === t[0] ? 'true' : undefined, onClick: function () { tab = t[0]; tick[1]() } }, t[1])
487
+ })),
488
+ h('div', { 'data-dam-body': '' }, body),
489
+ h('div', { 'data-dam-resize': '', title: '拖动调整大小', onPointerDown: resizeMove }))
490
+ }
491
+
492
+ // ───────────────────────── 设置页 ─────────────────────────
493
+ var STYLE_OPTIONS = [['auto', '由内容决定'], ['life', '生活化'], ['professional', '专业性']]
494
+ function SettingsPage() {
495
+ var tick = useTick()
496
+ var cfg = null
497
+ var busy = false
498
+ var msg = ''
499
+ var err = ''
500
+ useEffect(function () {
501
+ var alive = true
502
+ apiGet(API.config).then(function (d) { if (alive) { cfg = d.config; tick[1]() } }).catch(function (e) { err = e.message; tick[1]() })
503
+ return function () { alive = false }
504
+ }, [])
505
+ if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
506
+ function set(key, value) { cfg[key] = value; tick[1]() }
507
+ function save() {
508
+ if (busy) return
509
+ busy = true; msg = ''; err = ''
510
+ apiPost(API.config, cfg).then(function (d) { cfg = d.config; msg = '已保存 ✓'; busy = false; tick[1]() })
511
+ .catch(function (e) { err = e.message; busy = false; tick[1]() })
512
+ }
513
+ function field(label, control, hint) {
514
+ return h('div', null,
515
+ h('div', { 'data-dam-row': '' }, h('label', null, label), control),
516
+ hint ? h('div', { 'data-dam-hint': '' }, hint) : null)
517
+ }
518
+ return h('div', null,
519
+ h('div', { 'data-dam-hint': '' }, '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):'),
520
+ field('用户记忆目录', h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), '跨项目规则存放处,支持 ~ 开头;需有文件写权限。'),
521
+ field('项目记忆目录', h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), '相对各工作区的目录名(默认 .dsh-memory)。'),
522
+ field('注入记忆上下文', h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), '每次组装提示词时自动注入 <memory_system> 块。'),
523
+ field('注入预算(字符)', h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), '记忆块总预算,超出部分截断。'),
524
+ field('注入最近日志天数', h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 3) } }), '会话开始时注入最近 N 天的工作日志尾部。'),
525
+ field('每日反思', h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), '昨天有工作日志时,会话首轮主动呈现昨日反思。'),
526
+ field('反思风格', h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
527
+ STYLE_OPTIONS.map(function (o) { return h('option', { key: o[0], value: o[0] }, o[1]) })), '生活化 / 专业性 / 由内容决定。'),
528
+ h('div', { 'data-dam-row': '' },
529
+ h('button', { 'data-dam-btn': '', onClick: save, disabled: busy }, busy ? '保存中…' : '保存设置'),
530
+ msg ? h('span', null, msg) : null),
531
+ err ? h('div', { 'data-dam-error': '' }, err) : null)
532
+ }
533
+
534
+ // ───────────────────────── 插件挂载 ─────────────────────────
535
+ function apply(ctx) {
536
+ try { ensureStyle() } catch (e) { console.warn('[dsh-auto-memory] style inject failed', e) }
537
+ ctx.effect(function () {
538
+ return function () {
539
+ var tag = document.getElementById(STYLE_ID)
540
+ if (tag) tag.remove()
541
+ if (closeTimer) { clearTimeout(closeTimer); closeTimer = null }
542
+ }
543
+ }, 'dsh-auto-memory: styles')
544
+
545
+ var slots = ctx.slots
546
+ if (!slots) { console.warn('[dsh-auto-memory] slots service unavailable'); return }
547
+
548
+ try {
549
+ slots.inject('sidebar.footer.action', function () {
550
+ return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: '记忆' }, function () { return h(SidebarButton) })
551
+ })
552
+ slots.inject('shell.overlay', function () {
553
+ return slots.register({ name: 'shell.overlay', id: 'auto-memory', order: 5 }, function () { return h(MemoryPanel) })
554
+ })
555
+ slots.inject('settings.section', function () {
556
+ return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: '自动记忆' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
557
+ })
558
+ } catch (e) {
559
+ console.warn('[dsh-auto-memory] slot registration failed', e)
560
+ }
561
+ console.log('[dsh-auto-memory] client ready: sidebar entry + panel + settings page')
562
+ }
563
+
564
+ exports.inject = ['slots']
565
+ exports.apply = apply
566
+ return module.exports
567
+ },
568
+ })
package/package.json CHANGED
@@ -1,46 +1,46 @@
1
- {
2
- "name": "@a9i5k4/dsh-auto-memory",
3
- "description": "DSH 自动记忆插件:三层记忆(用户级/项目笔记/每日日志)自动注入与检索、每轮对话自动沉淀、每日反思、可视化面板与设置页,支持继承其他 AI 工具的记忆。",
4
- "version": "0.1.17",
5
- "type": "module",
6
- "main": "lib/index.js",
7
- "exports": {
8
- ".": "./lib/index.js",
9
- "./client": "./lib/client.js",
10
- "./package.json": "./package.json"
11
- },
12
- "files": [
13
- "lib",
14
- "cordis.patch.yml"
15
- ],
16
- "dsh": {
17
- "bundle": {
18
- "patch": "./cordis.patch.yml"
19
- },
20
- "client": {
21
- "inject": [
22
- "@deepseek-ai/dsh-client-runtime",
23
- "@deepseek-ai/dsh-client-connection",
24
- "@deepseek-ai/dsh-client-ui-slots",
25
- "@deepseek-ai/dsh-client-ui-settings",
26
- "@deepseek-ai/dsh-client-ui-sidebar"
27
- ],
28
- "platform": "web"
29
- }
30
- },
31
- "keywords": [
32
- "dsh",
33
- "deepseek-harness",
34
- "memory",
35
- "plugin",
36
- "auto-memory"
37
- ],
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/Aik358/dsh-auto-memory.git"
41
- },
42
- "peerDependencies": {
43
- "@deepseek-ai/cordis": "^4.0.1"
44
- },
45
- "license": "BSD-3-Clause"
46
- }
1
+ {
2
+ "name": "@a9i5k4/dsh-auto-memory",
3
+ "description": "DSH 自动记忆插件:三层记忆(用户级/项目笔记/每日日志)自动注入与检索、每轮对话自动沉淀、每日反思、可视化面板与设置页,支持继承其他 AI 工具的记忆。",
4
+ "version": "0.1.18",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "exports": {
8
+ ".": "./lib/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./package.json": "./package.json"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "cordis.patch.yml"
15
+ ],
16
+ "dsh": {
17
+ "bundle": {
18
+ "patch": "./cordis.patch.yml"
19
+ },
20
+ "client": {
21
+ "inject": [
22
+ "@deepseek-ai/dsh-client-runtime",
23
+ "@deepseek-ai/dsh-client-connection",
24
+ "@deepseek-ai/dsh-client-ui-slots",
25
+ "@deepseek-ai/dsh-client-ui-settings",
26
+ "@deepseek-ai/dsh-client-ui-sidebar"
27
+ ],
28
+ "platform": "web"
29
+ }
30
+ },
31
+ "keywords": [
32
+ "dsh",
33
+ "deepseek-harness",
34
+ "memory",
35
+ "plugin",
36
+ "auto-memory"
37
+ ],
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/Aik358/dsh-auto-memory.git"
41
+ },
42
+ "peerDependencies": {
43
+ "@deepseek-ai/cordis": "^4.0.1"
44
+ },
45
+ "license": "BSD-3-Clause"
46
+ }