deepseek-plastic-memory 2.1.5 → 2.1.6

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.
Files changed (4) hide show
  1. package/README.md +4 -0
  2. package/client.js +47 -22
  3. package/index.js +52 -2
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -64,6 +64,10 @@ node memtree.mjs add M>习惯 改代码先备份
64
64
 
65
65
  模型想记东西不会直接落盘,先扔进待审队列。设置页「可塑性记忆」最上面就是它,每条后面三个按钮:改、驳、采纳。
66
66
 
67
+ 点采纳会问写进哪个记忆库(全局 / 各工作区都列出来);记忆库只有一个「全局」时不问,直接写进去。写到别的工作区就是把这条搬过去,原库的待审里会消失。
68
+
69
+ 工作区(记忆库)在「+ 工作区」里建。新建时会连默认分区一起建好:`M` 主人、`P` 约定、`C` 续缘、`R` 喜好、`T` 时间线,不用再手动开。
70
+
67
71
  你自己写走「自己写一条」,选分区填一行,直接生效。
68
72
 
69
73
  ## 会忘
package/client.js CHANGED
@@ -61,13 +61,14 @@ window.__ModuleLoader__.load({
61
61
  }, [])
62
62
  }
63
63
 
64
- function PendingBox({ scope, folders, onChanged }) {
64
+ function PendingBox({ scope, scopes, folders, onChanged }) {
65
65
  const [rows, setRows] = React.useState([])
66
66
  const [msg, setMsg] = React.useState('')
67
67
  const [form, setForm] = React.useState(false)
68
68
  const [k, setK] = React.useState('')
69
69
  const [text, setText] = React.useState('')
70
70
  const [editing, setEditing] = React.useState(null)
71
+ const [pick, setPick] = React.useState(null) // 采纳时选记忆库:{ id, target }
71
72
  const load = React.useCallback(async () => {
72
73
  try { const d = await get('/pending', scope); setRows(d.rows || []) }
73
74
  catch (e) { setMsg(String((e && e.message) || e)) }
@@ -90,6 +91,16 @@ window.__ModuleLoader__.load({
90
91
  setText('')
91
92
  setForm(false)
92
93
  }
94
+ // 只有全局一个记忆库就直接采纳;有多个先问写进哪个
95
+ const startApprove = (r) => {
96
+ const list = scopes && scopes.length ? scopes : [{ id: 'global', label: '全局' }]
97
+ if (list.length <= 1) { act('/approve', { id: r.id }); return }
98
+ setPick({ id: r.id, target: scope || list[0].id })
99
+ }
100
+ const confirmApprove = async (id, target) => {
101
+ await act('/approve', { id, target })
102
+ setPick(null)
103
+ }
93
104
  return h('div', { className: 'pm-card' },
94
105
  h('div', { className: 'pm-head' },
95
106
  h('span', null, '待审批', h('span', { className: 'pm-badge' }, rows.length)),
@@ -106,25 +117,39 @@ window.__ModuleLoader__.load({
106
117
  msg ? h('div', { className: 'pm-msg' }, msg) : null,
107
118
  rows.length === 0
108
119
  ? h('div', { className: 'pm-empty' }, '没有待审的记忆')
109
- : rows.map((r) => editing === r.id
110
- ? h('div', { key: r.id, className: 'pm-form' },
111
- h('span', { className: 'pm-k' }, r.k),
112
- h('input', {
113
- className: 'pm-inp', defaultValue: r.text, autoFocus: true,
114
- id: 'pm-edit-' + r.id,
115
- onKeyDown: (e) => { if (e.key === 'Enter') act('/edit', { id: r.id, text: e.currentTarget.value }) },
116
- }),
117
- h('button', { className: 'pm-btn', onClick: () => setEditing(null) }, '取消'),
118
- h('button', {
119
- className: 'pm-btn primary',
120
- onClick: () => { const el = document.getElementById('pm-edit-' + r.id); act('/edit', { id: r.id, text: el && el.value }) },
121
- }, '存'))
122
- : h('div', { key: r.id, className: 'pm-row' },
123
- h('span', { className: 'pm-k' }, r.k),
124
- h('span', { className: 'pm-t', title: r.text }, r.text),
125
- h('button', { className: 'pm-btn', onClick: () => setEditing(r.id) }, '改'),
126
- h('button', { className: 'pm-btn', onClick: () => act('/reject', { id: r.id }) }, '驳'),
127
- h('button', { className: 'pm-btn primary', onClick: () => act('/approve', { id: r.id }) }, '采纳')))
120
+ : rows.map((r) => {
121
+ const row = editing === r.id
122
+ ? h('div', { key: 'e' + r.id, className: 'pm-form' },
123
+ h('span', { className: 'pm-k' }, r.k),
124
+ h('input', {
125
+ className: 'pm-inp', defaultValue: r.text, autoFocus: true,
126
+ id: 'pm-edit-' + r.id,
127
+ onKeyDown: (e) => { if (e.key === 'Enter') act('/edit', { id: r.id, text: e.currentTarget.value }) },
128
+ }),
129
+ h('button', { className: 'pm-btn', onClick: () => setEditing(null) }, '取消'),
130
+ h('button', {
131
+ className: 'pm-btn primary',
132
+ onClick: () => { const el = document.getElementById('pm-edit-' + r.id); act('/edit', { id: r.id, text: el && el.value }) },
133
+ }, '存'))
134
+ : h('div', { key: 'r' + r.id, className: 'pm-row' },
135
+ h('span', { className: 'pm-k' }, r.k),
136
+ h('span', { className: 'pm-t', title: r.text }, r.text),
137
+ h('button', { className: 'pm-btn', onClick: () => setEditing(r.id) }, '改'),
138
+ h('button', { className: 'pm-btn', onClick: () => act('/reject', { id: r.id }) }, '驳'),
139
+ h('button', { className: 'pm-btn primary', onClick: () => startApprove(r) }, '采纳'))
140
+ const chooser = pick && pick.id === r.id
141
+ ? h('div', { key: 'p' + r.id, className: 'pm-form' },
142
+ h('span', { className: 'pm-k' }, '写进'),
143
+ h('select', {
144
+ className: 'pm-sel', value: pick.target,
145
+ onChange: (e) => setPick({ id: r.id, target: e.target.value }),
146
+ }, (scopes && scopes.length ? scopes : [{ id: 'global', label: '全局' }])
147
+ .map((s) => h('option', { key: s.id, value: s.id }, s.cwd ? s.label + '(' + s.cwd + ')' : s.label))),
148
+ h('button', { className: 'pm-btn', onClick: () => setPick(null) }, '取消'),
149
+ h('button', { className: 'pm-btn primary', onClick: () => confirmApprove(r.id, pick.target) }, '写进去'))
150
+ : null
151
+ return h(React.Fragment, { key: r.id }, row, chooser)
152
+ })
128
153
  )
129
154
  }
130
155
 
@@ -289,7 +314,7 @@ window.__ModuleLoader__.load({
289
314
  if (!cwd) { setMsg('填个工作区路径'); return }
290
315
  try {
291
316
  const r = await postJson('/bind', { cwd })
292
- if (r.ok) { setMsg('已建 ' + r.label); setWsCwd(''); setWsOpen(false); await loadFolders() }
317
+ if (r.ok) { setMsg('已建 ' + r.label + (r.folders ? ',默认 ' + r.folders + ' 个分区已建好' : '')); setWsCwd(''); setWsOpen(false); await loadFolders() }
293
318
  else setMsg(r.error || '失败')
294
319
  } catch (e) { setMsg(String((e && e.message) || e)) }
295
320
  }
@@ -323,7 +348,7 @@ window.__ModuleLoader__.load({
323
348
  hits.length === 0
324
349
  ? h('div', { className: 'pm-empty' }, '没有匹配的记忆')
325
350
  : h('div', { className: 'pm-list' }, hits.map((line, i) => h('div', { key: i, className: 'pm-row' }, h('span', { className: 'pm-t' }, line))))) : null,
326
- h(PendingBox, { scope, folders, onChanged: loadFolders }),
351
+ h(PendingBox, { scope, scopes, folders, onChanged: loadFolders }),
327
352
  h('div', { style: { display: 'flex', minHeight: 260 } },
328
353
  h(Browser, { scope, folders, cur, setCur, reload: () => loadFolders() })))
329
354
  }
package/index.js CHANGED
@@ -55,7 +55,10 @@ function bindWorkspace(cwd) {
55
55
  mkdirSync(dir, { recursive: true })
56
56
  const meta = { cwd: String(cwd), name: basename(String(cwd)) }
57
57
  writeFileSync(join(dir, 'meta.json'), JSON.stringify(meta, null, 1), 'utf8')
58
- return { ok: true, id: 'ws:' + slug, label: meta.name }
58
+ // 新建的记忆库直接把默认分区(M主人 P约定 C续缘 R喜好 T时间线)建出来
59
+ const out = runIn(['init'], 'ws:' + slug)
60
+ const folders = (readJson(join(dir, 'folders.json'), {}) || {}).folders || []
61
+ return { ok: true, id: 'ws:' + slug, label: meta.name, folders: folders.length, out }
59
62
  }
60
63
 
61
64
  function listDshWorkspaces() {
@@ -129,6 +132,48 @@ function readPendingList(scope) {
129
132
  return Array.isArray(a) ? a : []
130
133
  }
131
134
 
135
+ function writePendingList(scope, rows) {
136
+ const home = homeOf(scope)
137
+ mkdirSync(home, { recursive: true })
138
+ writeFileSync(join(home, '_pending.json'), JSON.stringify(rows, null, 1), 'utf8')
139
+ }
140
+
141
+ // 直接写一条已采纳的记忆(跨记忆库采纳时用,不走命令行)
142
+ function appendMemo(scope, folderK, text, flag, ts) {
143
+ const home = homeOf(scope)
144
+ const folders = (readJson(join(home, 'folders.json'), {}) || {}).folders || []
145
+ const hasKids = (k) => folders.some((x) => x.parent === k)
146
+ let f = folders.find((x) => x.k === folderK)
147
+ if (f && !f.parent && hasKids(f.k)) f = folders.find((x) => x.parent === f.k)
148
+ if (!f) f = folders.find((x) => x.k === 'R' && !hasKids('R'))
149
+ if (!f) f = folders.find((x) => !x.parent && !hasKids(x.k))
150
+ if (!f) f = folders.find((x) => !x.parent)
151
+ if (!f) return { ok: false, error: '目标记忆库还没有分区' }
152
+ const body = String(text || '').replace(/[\r\n|]+/g, ' ').trim()
153
+ if (!body) return { ok: false, error: '空内容' }
154
+ const dir = join(home, f.k)
155
+ mkdirSync(dir, { recursive: true })
156
+ const id = Math.random().toString(36).slice(2, 6)
157
+ appendFileSync(join(dir, 'notes.md'), [id, ts || Date.now(), flag || '', body].join('|') + String.fromCharCode(10), 'utf8')
158
+ return { ok: true, k: f.k }
159
+ }
160
+
161
+ // 把待审条目采纳到指定记忆库:同库走命令行,跨库先落到目标库再删源待审
162
+ function approveTo(sourceScope, targetScope, id) {
163
+ const src = sourceScope || 'global'
164
+ const dst = targetScope || src
165
+ if (dst === src) return { ok: true, out: runIn(['approve', String(id)], src) }
166
+ const rows = readPendingList(src)
167
+ const i = rows.findIndex((x) => String(x.id) === String(id))
168
+ if (i < 0) return { ok: false, out: '无此条' }
169
+ const it = rows[i]
170
+ const r = appendMemo(dst, it.k, it.text, it.flag, it.ts)
171
+ if (!r.ok) return { ok: false, out: r.error }
172
+ rows.splice(i, 1)
173
+ writePendingList(src, rows)
174
+ return { ok: true, out: '采纳到 ' + r.k + ' 剩' + rows.length }
175
+ }
176
+
132
177
  // DSH 要求插件工具 output.schema 是 object,所以统一包一层 { text }
133
178
  const asText = (v) => ({ text: typeof v === 'string' ? v : JSON.stringify(v ?? '', null, 2) })
134
179
  const textOutput = {
@@ -247,7 +292,12 @@ export function apply(ctx) {
247
292
  if (req.method === 'POST' && p === '/add-now') { const b = await readBody(req); return json(res, 200, { ok: true, out: directAdd(b.scope || qs, String(b.folder), String(b.text)) }) }
248
293
  if (req.method === 'POST' && p === '/edit-item') { const b = await readBody(req); return json(res, 200, { ok: true, out: runIn(['edit-mem', String(b.k), String(b.id), String(b.text)], b.scope || qs) }) }
249
294
  if (req.method === 'POST' && p === '/del-item') { const b = await readBody(req); return json(res, 200, { ok: true, out: runIn(['rm', String(b.k), String(b.id), 'force'], b.scope || qs) }) }
250
- if (req.method === 'POST' && p === '/approve') { const b = await readBody(req); return json(res, 200, { ok: true, out: runIn(['approve', String(b.id)], b.scope || qs) }) }
295
+ if (req.method === 'POST' && p === '/approve') {
296
+ const b = await readBody(req)
297
+ const src = b.scope || qs
298
+ const target = b.target || src
299
+ return json(res, 200, { ok: true, ...approveTo(src, target, b.id) })
300
+ }
251
301
  if (req.method === 'POST' && p === '/reject') { const b = await readBody(req); return json(res, 200, { ok: true, out: runIn(['reject', String(b.id)], b.scope || qs) }) }
252
302
  if (req.method === 'POST' && p === '/edit') { const b = await readBody(req); return json(res, 200, { ok: true, out: runIn(['edit', String(b.id), String(b.text)], b.scope || qs) }) }
253
303
  return json(res, 404, { ok: false, error: 'unknown' })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepseek-plastic-memory",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "description": "DeepSeek 可塑性记忆:给 DSH 用的分区式长期记忆,带遗忘曲线",
5
5
  "type": "module",
6
6
  "main": "index.js",