deepseek-plastic-memory 3.3.8 → 3.4.0

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/SKILL.md CHANGED
@@ -28,7 +28,8 @@ description: 可塑性记忆(Plastic Memory)—— 按可自由扩展的子
28
28
 
29
29
  ```bash
30
30
  N=<node 绝对路径>; M=~/.dsh/plastic-memory/memtree.mjs
31
- $N $M idx # 一行索引:分区 + 条数(最省)
31
+ $N $M all # 一次性读出全部记忆(默认用这个)
32
+ $N $M idx # 一行索引:分区 + 条数(只想看结构时用)
32
33
  $N $M open M # 翻一个分区:id 天数 标记 内容
33
34
  $N $M add M 爱吃辣 # 提交一条待审(能短则短)
34
35
  $N $M add-now M 爱吃辣 # 直接写入,跳过审批(自己写用)
@@ -120,11 +121,11 @@ $N $M tlog 修了崩溃问题
120
121
  - 一天最多 5 条左右,超了就先挑最值的
121
122
  - 记完别汇报"已保存",随口带一句"这个记下了"就够了
122
123
 
123
- ## 回答前该翻哪个分区
124
+ ## 回答前怎么读记忆
124
125
 
125
- 先 `idx` 看一眼(一行,最便宜),再按问题挑:
126
+ **默认全量读**:直接 `plastic_memory_all`(或 `$N $M all`)把每个分区都读一遍,再从里面找要用的。不要先 `idx` 再挑分区,也不要只翻一两个区。
126
127
 
127
- | 他问的 | 翻哪 |
128
+ | 他问的 | 重点看哪个区 |
128
129
  | --- | --- |
129
130
  | 我是谁 / 我住哪 / 我的情况 | `M`,不够再看 `M>行为` |
130
131
  | 我们约过什么 / 你答应我的 | `P` |
@@ -133,7 +134,7 @@ $N $M tlog 修了崩溃问题
133
134
  | 最近发生过什么 | `T` |
134
135
  | 工作、项目上的事 | `X0 工作` 或工作区记忆库 |
135
136
 
136
- **规矩:一次最多翻两个分区。** 翻完发现不对再换,别把整个库都揭开。闲聊、打招呼、单纯接梗——不用翻,直接聊。
137
+ **规矩:需要回忆就读全部,不缺这点 token。** 全量读完再按上表心里过一遍,比反复挑分区翻错更省事。闲聊、打招呼、单纯接梗——不用读,直接聊。
137
138
 
138
139
  ## 换缸提示
139
140
 
package/index.js CHANGED
@@ -288,9 +288,16 @@ export function apply(ctx) {
288
288
  return asText('action 只认 dump / move / done')
289
289
  },
290
290
  })
291
+ ctx.tools.register({
292
+ name: 'plastic_memory_all',
293
+ description: '可塑性记忆:一次性读出全部记忆(所有分区 + 待审),直接从这一份里找你需要的东西,不要再用 idx 挑分区。每次需要回忆主人信息/约定/上次聊到哪,都先调这个全量读一遍。',
294
+ parameters: obj({}),
295
+ output: textOutput,
296
+ async execute() { return asText(run(['all'])) },
297
+ })
291
298
  ctx.tools.register({
292
299
  name: 'plastic_memory_idx',
293
- description: '可塑性记忆:一行索引,列出所有分区和条数(M主人:3 P约定:1 ...)。看这个再决定翻哪个分区;一次最多翻两个,闲聊不用翻。',
300
+ description: '可塑性记忆:一行索引,只列分区和条数(想看结构时用)。要看内容请直接用 plastic_memory_all 全量读,不要靠 idx 挑分区。',
294
301
  parameters: obj({}),
295
302
  output: textOutput,
296
303
  async execute() { return asText(run(['idx'])) },
package/memtree.mjs CHANGED
@@ -380,6 +380,27 @@ function cmdMove(args) {
380
380
  console.log('ok ' + id + ' ' + from + ' -> ' + to)
381
381
  }
382
382
 
383
+ function cmdAll() {
384
+ const out = []
385
+ for (const f of S.folders) {
386
+ const rows = readNotes(f.k)
387
+ if (!rows.length) continue
388
+ const tag = f.parent ? (S.folders.find(x => x.k === f.parent)?.name || f.parent) + '>' + f.name : f.name
389
+ out.push('## ' + tag + ' (' + f.k + ')')
390
+ for (const r of rows) {
391
+ const flag = r.flag === '*' ? ' [重要]' : r.flag === '✓' ? ' [完成]' : ''
392
+ out.push(r.id + flag + ' ' + r.text)
393
+ }
394
+ out.push('')
395
+ }
396
+ const pend = readPending()
397
+ if (pend.length) {
398
+ out.push('## 待审 (' + pend.length + ' 条)')
399
+ for (const x of pend) out.push(x.id + ' ' + x.text)
400
+ }
401
+ console.log(out.length ? out.join(String.fromCharCode(10)) : '还没有记忆')
402
+ }
403
+
383
404
  function cmdInit() {
384
405
  fs.mkdirSync(HOME, { recursive: true })
385
406
  fs.mkdirSync(TRASH, { recursive: true })
@@ -388,6 +409,6 @@ function cmdInit() {
388
409
  console.log('init ok ' + HOME)
389
410
  }
390
411
  const [cmd, ...args] = process.argv.slice(2)
391
- const MAP = { idx: cmdIdx, open: cmdOpen, add: (a) => cmdAdd(a, false), 'edit-mem': cmdEditMem, search: cmdSearch, tlog: cmdTLog, pending: cmdPending, approve: cmdApprove, reject: cmdReject, edit: cmdEdit, done: cmdDone, rm: cmdRm, gc: cmdGc, mk: cmdMk, rmfolder: cmdRmFolder, dump: cmdDump, move: cmdMove, init: cmdInit, backups: cmdBackups, restore: cmdRestore }
412
+ const MAP = { idx: cmdIdx, open: cmdOpen, add: (a) => cmdAdd(a, false), 'edit-mem': cmdEditMem, search: cmdSearch, tlog: cmdTLog, pending: cmdPending, approve: cmdApprove, reject: cmdReject, edit: cmdEdit, done: cmdDone, rm: cmdRm, gc: cmdGc, mk: cmdMk, rmfolder: cmdRmFolder, dump: cmdDump, move: cmdMove, all: cmdAll, init: cmdInit, backups: cmdBackups, restore: cmdRestore }
392
413
  if (!MAP[cmd]) { console.log('用法: idx|open|add|pending|approve|reject|edit|done|rm|rmfolder|gc|backups|restore|mk|init'); process.exit(1) }
393
414
  MAP[cmd](args)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepseek-plastic-memory",
3
- "version": "3.3.8",
3
+ "version": "3.4.0",
4
4
  "description": "DeepSeek 可塑性记忆:给 DSH 用的分区式长期记忆,带遗忘曲线",
5
5
  "type": "module",
6
6
  "main": "index.js",
package/public/sky/app.js CHANGED
@@ -1613,26 +1613,30 @@ function applyThemeGlyphs() {
1613
1613
 
1614
1614
  function applyDim() {
1615
1615
  const sel = state.sel
1616
+ const searching = !!state.query
1616
1617
  // 只有选中「星辰」才虚化别的星云;点开一条记忆不动星系明暗
1617
1618
  const starSel = sel && (sel.kind === 'main' || sel.kind === 'sub')
1618
1619
  const focusK = starSel ? (sel.kind === 'main' ? sel.k : (subs.get(sel.k)?.parentK || '')) : ''
1619
1620
  const focusMain = focusK ? (subs.has(focusK) ? subs.get(focusK).parentK : focusK) : ''
1620
1621
 
1622
+ // 搜索是全局的:不管当前选中了哪个分区,命中的记忆都要亮起来
1621
1623
  for (const [k, node] of mains) {
1624
+ if (searching) { node.dimTarget = 0.5; continue }
1622
1625
  const related = !focusMain || k === focusMain
1623
1626
  node.dimTarget = related ? 1 : 0.16
1624
1627
  }
1625
1628
  for (const [k, node] of subs) {
1629
+ if (searching) { node.dimTarget = 0.4; continue }
1626
1630
  const related = !focusMain || node.parentK === focusMain
1627
1631
  const onPath = sel && sel.kind === 'sub' ? sel.k === k : related
1628
1632
  node.dimTarget = onPath ? 1 : (related ? 0.55 : 0.12)
1629
1633
  }
1630
- const dimMem = !!focusMain || state.pinnedOnly || !!state.query
1634
+ const dimMem = !!focusMain || state.pinnedOnly || searching
1631
1635
  const onlyMem = !!(sel && sel.kind === 'mem')
1632
1636
  for (const [id, node] of mems) {
1633
1637
  let target = 1
1634
- if (state.pinnedOnly && !node.mem.pinned) target = 0.15
1635
- else if (state.query) target = state.matches.has(id) ? 1 : 0.12
1638
+ if (searching) target = state.matches.has(id) ? 1 : 0.1
1639
+ else if (state.pinnedOnly && !node.mem.pinned) target = 0.15
1636
1640
  else if (onlyMem) target = sel.id === id ? 1 : 0.55
1637
1641
  else if (focusMain && node.folder !== focusK && !(subs.has(node.folder) && subs.get(node.folder).parentK === focusMain)) target = 0.18
1638
1642
  node.dimTarget = dimMem || sel ? target : 1
@@ -2283,7 +2287,15 @@ async function boot() {
2283
2287
  } else toast(r.error || '没删掉', 'bad')
2284
2288
  })
2285
2289
  }
2286
- $('search').addEventListener('input', (e) => {
2290
+ $('search').addEventListener('focus', () => {
2291
+ if (state.sel && state.sel.kind !== 'mem') {
2292
+ state.sel = null
2293
+ applyDim()
2294
+ renderPanel()
2295
+ }
2296
+ })
2297
+
2298
+ $('search').addEventListener('input', (e) => {
2287
2299
  clearTimeout(searchTimer)
2288
2300
  const v = e.target.value
2289
2301
  searchTimer = setTimeout(() => onSearch(v), 130)
@@ -1613,26 +1613,30 @@ function applyThemeGlyphs() {
1613
1613
 
1614
1614
  function applyDim() {
1615
1615
  const sel = state.sel
1616
+ const searching = !!state.query
1616
1617
  // 只有选中「星辰」才虚化别的星云;点开一条记忆不动星系明暗
1617
1618
  const starSel = sel && (sel.kind === 'main' || sel.kind === 'sub')
1618
1619
  const focusK = starSel ? (sel.kind === 'main' ? sel.k : (subs.get(sel.k)?.parentK || '')) : ''
1619
1620
  const focusMain = focusK ? (subs.has(focusK) ? subs.get(focusK).parentK : focusK) : ''
1620
1621
 
1622
+ // 搜索是全局的:不管当前选中了哪个分区,命中的记忆都要亮起来
1621
1623
  for (const [k, node] of mains) {
1624
+ if (searching) { node.dimTarget = 0.5; continue }
1622
1625
  const related = !focusMain || k === focusMain
1623
1626
  node.dimTarget = related ? 1 : 0.16
1624
1627
  }
1625
1628
  for (const [k, node] of subs) {
1629
+ if (searching) { node.dimTarget = 0.4; continue }
1626
1630
  const related = !focusMain || node.parentK === focusMain
1627
1631
  const onPath = sel && sel.kind === 'sub' ? sel.k === k : related
1628
1632
  node.dimTarget = onPath ? 1 : (related ? 0.55 : 0.12)
1629
1633
  }
1630
- const dimMem = !!focusMain || state.pinnedOnly || !!state.query
1634
+ const dimMem = !!focusMain || state.pinnedOnly || searching
1631
1635
  const onlyMem = !!(sel && sel.kind === 'mem')
1632
1636
  for (const [id, node] of mems) {
1633
1637
  let target = 1
1634
- if (state.pinnedOnly && !node.mem.pinned) target = 0.15
1635
- else if (state.query) target = state.matches.has(id) ? 1 : 0.12
1638
+ if (searching) target = state.matches.has(id) ? 1 : 0.1
1639
+ else if (state.pinnedOnly && !node.mem.pinned) target = 0.15
1636
1640
  else if (onlyMem) target = sel.id === id ? 1 : 0.55
1637
1641
  else if (focusMain && node.folder !== focusK && !(subs.has(node.folder) && subs.get(node.folder).parentK === focusMain)) target = 0.18
1638
1642
  node.dimTarget = dimMem || sel ? target : 1
@@ -2283,7 +2287,15 @@ async function boot() {
2283
2287
  } else toast(r.error || '没删掉', 'bad')
2284
2288
  })
2285
2289
  }
2286
- $('search').addEventListener('input', (e) => {
2290
+ $('search').addEventListener('focus', () => {
2291
+ if (state.sel && state.sel.kind !== 'mem') {
2292
+ state.sel = null
2293
+ applyDim()
2294
+ renderPanel()
2295
+ }
2296
+ })
2297
+
2298
+ $('search').addEventListener('input', (e) => {
2287
2299
  clearTimeout(searchTimer)
2288
2300
  const v = e.target.value
2289
2301
  searchTimer = setTimeout(() => onSearch(v), 130)