deepseek-plastic-memory 2.1.4 → 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.
- package/README.md +4 -0
- package/client.js +47 -22
- package/index.js +52 -2
- 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) =>
|
|
110
|
-
|
|
111
|
-
h('
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
h('
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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') {
|
|
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' })
|