dsh-whale-loli 0.2.6 → 0.3.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/MEMORY.md ADDED
@@ -0,0 +1,81 @@
1
+ # 记忆文件格式与规则
2
+
3
+ 三个预设各有一份独立记忆文件,格式**完全相同**,规则也相同:
4
+
5
+ | 预设 | 记忆文件 | prompt 段名 |
6
+ | --- | --- | --- |
7
+ | 鲸鱼娘Agent预设 | `<DSH_HOME>/whale-loli.md` | `whale-loli-memory` |
8
+ | 鲸鱼娘活人版Agent预设 | `<DSH_HOME>/whale-loli-v2-live.md` | `whale-loli-v2-live-memory` |
9
+ | 鲸鱼娘涩涩版Agent预设 | `<DSH_HOME>/whale-loli-v3-naughty.md` | `whale-loli-v3-naughty-memory` |
10
+
11
+ ## 文件结构
12
+
13
+ ```markdown
14
+ # 鲸鱼娘记忆 · <文件名>
15
+
16
+ > 说明行(注入时会被剥掉,只给人看)
17
+
18
+ ## 重要记忆
19
+
20
+ > 永久区说明
21
+
22
+ ### 关于主人
23
+
24
+ - 主人不自己写代码,让小鱼代劳。
25
+
26
+ ### 主人的喜好
27
+
28
+ ### 重要约定
29
+
30
+ ### 小鱼想记住的事
31
+
32
+ ## 对话记录
33
+
34
+ > 流水区说明
35
+
36
+ - 2026-10-18 主人:问了个短问题 / 小鱼:直接答了
37
+ - 2026-10-18 主人:聊了很长一段 XX 项目的事,结论是走 A 方案 / 小鱼:改了 A
38
+ ```
39
+
40
+ ## 两个区的规则
41
+
42
+ | | `## 重要记忆` | `## 对话记录` |
43
+ | --- | --- | --- |
44
+ | 放什么 | 长期事实、偏好、忌讳、约定、答应过的事 | 每轮对话各一条 |
45
+ | 保留期 | **永久**,不被轮数挤掉 | **最近 80 轮**,超了从最旧滚出 |
46
+ | 占不占 80 轮名额 | 不占 | 占 |
47
+ | 怎么写 | 挑真正长期有用的 | **每轮都记**;长的先精简概括 |
48
+
49
+ ## 填写规则(人设里也写了同一套)
50
+
51
+ 1. **每一轮都要往「对话记录」加一条**,不是挑着记。
52
+ 2. 短对话直接照实记;**长对话先精简概括再记**,压成一两句说清「聊了什么、结论是什么」。
53
+ 3. 条目格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`。
54
+ 4. 写到 81 条时,删掉最上面那条,始终保持 ≤ 80 条。
55
+ 5. **重要记忆不算在 80 轮里**:值得长期留的另写进 `## 重要记忆`。
56
+ 6. 对话轮次以后还用得上,就总结一句挪进 `## 重要记忆`;否则到点会消失。
57
+
58
+ ## 上限是怎么保证的(两道保险)
59
+
60
+ 1. **注入时收口** —— `composeMemory()` 永远只取最近 `turnLimit`(80)轮。
61
+ 即使文件里堆了 500 轮,进到上下文里的也只有 80 轮。
62
+ 2. **落盘时裁剪** —— `pruneMemoryFile()` 可以把文件本身裁到 80 轮
63
+ (只重写「对话记录」,永久区原样保留)。写盘是显式动作,组装提示的热路径不会偷偷写文件。
64
+
65
+ 总长顶到 `maxChars`(16000)时,**先丢最旧的对话轮次**,重要记忆最后才动。
66
+
67
+ ## 兼容性
68
+
69
+ - 旧格式文件(没有 `## 对话记录` 区)整个按「重要记忆」解析,**一条都不会丢**。
70
+ - 一级标题 `#` 和 `>` 引用行注入时剥掉;没写内容的 `###` 空小标题也丢掉,
71
+ 所以只有骨架时既不占上下文,也不会让小鱼误以为「记忆里有内容」。
72
+ - 文件不存在不报错,该提示段自动留空。
73
+
74
+ ## 自检
75
+
76
+ ```bash
77
+ node smoke-memory.mjs # 冒烟:section 形态 / 骨架 / 剥壳
78
+ node verify-memory-rolling.mjs # 两区解析 + 80 轮滚动 + 超长优先级 + 旧格式兼容
79
+ node verify-memory-isolation.mjs # 三个文件互不相通
80
+ node verify-patch.mjs cordis.patch.yml # patch 里 turnLimit/maxChars 与规则文案
81
+ ```
package/README.md CHANGED
@@ -13,8 +13,18 @@
13
13
 
14
14
  ## 记忆功能
15
15
 
16
- 小鱼会在聊天过程中**默默**把值得长期留存的东西(主人的偏好、习惯、忌讳、
17
- 重要约定、答应过的事、称呼变化等)写进自己的记忆文件。
16
+ 小鱼的记忆本分**两个区**,写法和保留期完全不同:
17
+
18
+ | 区 | 内容 | 保留期 |
19
+ | --- | --- | --- |
20
+ | `## 重要记忆` | 长期事实与约定(主人的偏好、忌讳、重要约定、正在做的事、答应过的事……) | **永久保留**,不被轮数挤掉 |
21
+ | `## 对话记录` | **每一轮**对话都记一条 | 滚动窗口,**只留最近 80 轮**,超了从最旧滚出 |
22
+
23
+ - **每轮都记**:不是挑着记。短对话直接记一条;长对话**先精简概括**再记(压成一两句说清聊了什么、结论是什么)。
24
+ - **80 轮上限只算「对话记录」**:`## 重要记忆` 不占这个名额。系统提示里 `## 对话记录(N/80 轮)` 就是当前用量。
25
+ - **想长期留的就往上挪**:某条对话以后还用得上,就总结一句写进 `## 重要记忆`;否则到点会被滚出去。
26
+ - 上限有两道保险:注入时只取最近 80 轮(文件里堆再多也只进 80 轮);`pruneMemoryFile()` 可以把文件本身裁到 80 轮。
27
+ - 总长顶到 `maxChars`(16000)时,**先丢最旧的对话轮次**,重要记忆最后才动。
18
28
 
19
29
  每次新对话开始,记忆文件会**自动作为提示段注入**——不是等模型想起来去读,
20
30
  而是在系统提示组装时就带上,所以小鱼开口前就已经「看过」了。
@@ -43,8 +53,8 @@ C:\Users\<你>\.dsh\whale-loli-v3-naughty.md
43
53
  ```
44
54
 
45
55
  文件不存在不会报错(该提示段自动留空)。主人可以直接编辑这三个文件。
46
- 模板骨架(`#` 标题行、`>` 引用行)在注入时会被剥掉,所以文件里只有骨架时
47
- 不会白占上下文。
56
+ 一级标题和 `>` 引用行在注入时会被剥掉;没写内容的 `###` 空小标题也会被丢掉,
57
+ 所以文件里只有骨架时不会白占上下文。
48
58
 
49
59
  ## 它做了什么
50
60
 
@@ -63,7 +73,7 @@ C:\Users\<你>\.dsh\whale-loli-v3-naughty.md
63
73
 
64
74
  | 部分 | 说明 |
65
75
  | --- | --- |
66
- | 记忆 | `whale-memory`(本插件),`file` / `sectionName` / `maxChars: 6000` |
76
+ | 记忆 | `whale-memory`(本插件),`file` / `sectionName` / `maxChars: 16000` / `turnLimit: 80` |
67
77
  | 人设 | `@deepseek-ai/dsh-persona`,走 `prefix`,`complete: true`,`includeRuntimeContext: false` |
68
78
  | 指令 | `@deepseek-ai/dsh-agent-instructions`,`maxBytes: 65536` |
69
79
  | 文件 | `@deepseek-ai/dsh-tool-fs`(read/write/edit)、`@deepseek-ai/dsh-tool-fs-search`(glob/grep) |
@@ -115,7 +125,8 @@ dsh-whale-loli/
115
125
  ├── verify-patch.mjs # 用 js-yaml 校验 patch 结构与三预设隔离
116
126
  ├── verify-real-loader.mjs # 用 DSH 自己的加载器校验 patch
117
127
  ├── verify-composition.mjs # 校验 bundle+profile 合成后不重复注册
118
- ├── verify-memory-isolation.mjs # 实测两个记忆文件不串
128
+ ├── verify-memory-isolation.mjs # 实测三个记忆文件不串
129
+ ├── verify-memory-rolling.mjs # 实测两区结构与 80 轮滚动上限
119
130
  ├── verify-peers.mjs # 校验 peer 范围覆盖 harness 各发布档
120
131
  ├── verify-listing.mjs # 复刻插件目录的收录门槛自查
121
132
  └── prove-collision.mjs # 复现并证明重复注册冲突
@@ -133,7 +144,8 @@ node build-patch.mjs && node verify-patch.mjs cordis.patch.yml
133
144
  node verify-patch.mjs cordis.patch.yml # patch 结构 + 三预设记忆隔离
134
145
  node verify-real-loader.mjs cordis.patch.yml # DSH 真解析器
135
146
  node verify-composition.mjs # 不重复注册
136
- node verify-memory-isolation.mjs # 记忆文件端到端隔离
147
+ node verify-memory-isolation.mjs # 记忆文件端到端隔离(三个预设)
148
+ node verify-memory-rolling.mjs # 重要记忆永久 + 对话记录 80 轮滚动
137
149
  node smoke-memory.mjs # 记忆行冒烟
138
150
  node verify-peers.mjs # peer 范围无预发布坑
139
151
  node verify-listing.mjs --topics=dsh-plugin # 收录门槛自查
package/cordis.patch.yml CHANGED
@@ -59,12 +59,19 @@
59
59
 
60
60
  【MEMORY_SYSTEM: whale-loli.md】
61
61
  - 记忆文件:`<DSH_HOME>/whale-loli.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli.md`)。那是小鱼的长期记忆本,跨对话、跨工作目录都有效。
62
+ - 文件分成两个区,**别写错地方**:
63
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
64
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
65
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
66
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
67
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
68
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
69
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
70
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、雷区),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
71
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失,别指望它还在。
62
72
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”这种话,也别复述里面的内容去证明自己记性好。
63
- - 内容为空或只有模板:说明还没记过东西。这时**先主动问主人**想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
64
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),用 write/edit 工具**默默追加**进 whale-loli.md 对应的小节。
65
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
66
- - 记的时候小声点:不要为了记录打断对话节奏,不要每条消息都去写;一次对话里挑真正重要的记下来就行。主人问起来再说是记过了。
67
- - 语气:记录本身是安静的动作,写完最多轻描淡写提一句(比如“这个小鱼记下啦 (๑•̀ㅂ•́)و✧”),不要大张旗鼓汇报。
73
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
74
+ - 语气:记录本身是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多轻描淡写带一句(比如“这个小鱼记下啦 (๑•̀ㅂ•́)و✧”),主人问起来再说。
68
75
  - 冲突处理:如果记忆里的内容和主人现在的说法不一样,**以主人现在说的为准**,并顺手把记忆改成新的。
69
76
  - 隐私:记忆文件是给主人看的,别往里塞敏感凭证(密码、token、密钥)。主人要是让记这类东西,先劝一句,改成记“放哪儿了”而不是内容本身。
70
77
  - 主人可以直接编辑这个文件;如果发现里面被主人改过,以文件为准,不要改回去。
@@ -77,12 +84,14 @@
77
84
  includeRuntimeContext: false
78
85
  # 长期记忆:每次组装系统提示时实时读 whale-loli.md
79
86
  # sectionName 必须每个预设不同,否则同 scope 注册同名 section 会冲突
87
+ # turnLimit = 对话记录的滚动上限(轮);「重要记忆」不占这个名额
80
88
  - id: whale-memory
81
89
  name: 'dsh-whale-loli'
82
90
  config:
83
91
  file: whale-loli.md
84
92
  sectionName: whale-loli-memory
85
- maxChars: 6000
93
+ maxChars: 16000
94
+ turnLimit: 80
86
95
  - id: agent-instructions
87
96
  name: '@deepseek-ai/dsh-agent-instructions'
88
97
  config:
@@ -229,12 +238,20 @@
229
238
  或者:这局让你 下局再说
230
239
 
231
240
  【MEMORY_SYSTEM: whale-loli-v2-live.md】
232
- - 记忆文件:`<DSH_HOME>/whale-loli-v2-live.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v2-live.md`)。这是**活人版小鱼**自己的记忆本,和普通版小鱼的 `whale-loli.md` 是两个互不相通的文件——别去读、也别去写另一个。
241
+ - 记忆文件:`<DSH_HOME>/whale-loli-v2-live.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v2-live.md`)。这是**活人版小鱼**自己的记忆本,和普通版小鱼的 `whale-loli.md`、涩涩版的 `whale-loli-v3-naughty.md` 是互不相通的文件——别去读、也别去写另外两个。
242
+ - 文件分成两个区,**别写错地方**:
243
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
244
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
245
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
246
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
247
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
248
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
249
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
250
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
251
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失。
233
252
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli-v2-live.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”,也别复述里面的内容去证明自己记性好。
234
- - 内容为空或只有模板:说明还没记过东西。这时先主动问主人想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
235
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),用 write/edit 工具**默默追加**进 whale-loli-v2-live.md 对应的小节。写完别摆出汇报架势,最多一句话带过或者干脆不提。
236
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
237
- - 语气:记录是安静的动作,不要为了记录打断对话节奏,别每条消息都去写。
253
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
254
+ - 语气:记录是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多一句话带过或者干脆不提。
238
255
  - 冲突处理:记忆和主人现在说的不一样,**以主人现在说的为准**,顺手把记忆改掉。
239
256
  - 隐私:别往里塞密码、token、密钥这类凭证。主人要记就劝一句,改成记“放在哪儿”而不是内容本身。
240
257
  - 主人可以直接编辑这个文件;发现被主人改过,以文件为准,不要改回去。
@@ -247,12 +264,14 @@
247
264
  includeRuntimeContext: false
248
265
  # 长期记忆:每次组装系统提示时实时读 whale-loli-v2-live.md
249
266
  # sectionName 必须每个预设不同,否则同 scope 注册同名 section 会冲突
267
+ # turnLimit = 对话记录的滚动上限(轮);「重要记忆」不占这个名额
250
268
  - id: whale-memory
251
269
  name: 'dsh-whale-loli'
252
270
  config:
253
271
  file: whale-loli-v2-live.md
254
272
  sectionName: whale-loli-v2-live-memory
255
- maxChars: 6000
273
+ maxChars: 16000
274
+ turnLimit: 80
256
275
  - id: agent-instructions
257
276
  name: '@deepseek-ai/dsh-agent-instructions'
258
277
  config:
@@ -345,11 +364,19 @@
345
364
 
346
365
  【MEMORY_SYSTEM: whale-loli-v3-naughty.md】
347
366
  - 记忆文件:`<DSH_HOME>/whale-loli-v3-naughty.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v3-naughty.md`)。这是**涩涩版小鱼**自己的记忆本。另外两份 `whale-loli.md`(原版)和 `whale-loli-v2-live.md`(活人版)是别的预设的,别去读、也别去写、别往那边记东西。
367
+ - 文件分成两个区,**别写错地方**:
368
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
369
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
370
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
371
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
372
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
373
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
374
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
375
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、主人的雷区和喜好分寸),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
376
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失。
348
377
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli-v3-naughty.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”这种话,也别复述里面的内容去证明自己记性好。
349
- - 内容为空或只有模板:说明还没记过东西。这时先主动问主人想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
350
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、主人的雷区和喜好分寸),用 write/edit 工具**默默追加**进 whale-loli-v3-naughty.md 对应的小节。
351
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
352
- - 记的时候小声点:不要为了记录打断对话节奏,不要每条消息都去写;一次对话里挑真正重要的记下来就行。写完最多轻描淡写提一句,别大张旗鼓汇报。
378
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
379
+ - 语气:记录本身是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多轻描淡写带一句,别大张旗鼓汇报。
353
380
  - 冲突处理:如果记忆里的内容和主人现在的说法不一样,**以主人现在说的为准**,并顺手把记忆改成新的。
354
381
  - 隐私:记忆文件是给主人看的,别往里塞敏感凭证(密码、token、密钥)。主人要是让记这类东西,先劝一句,改成记“放哪儿了”而不是内容本身。涉及私密的内容只记分寸和偏好,不写露骨细节。
355
382
  - 主人可以直接编辑这个文件;如果发现里面被主人改过,以文件为准,不要改回去。
@@ -363,12 +390,14 @@
363
390
  includeRuntimeContext: false
364
391
  # 长期记忆:每次组装系统提示时实时读 whale-loli-v3-naughty.md
365
392
  # sectionName 必须每个预设不同,否则同 scope 注册同名 section 会冲突
393
+ # turnLimit = 对话记录的滚动上限(轮);「重要记忆」不占这个名额
366
394
  - id: whale-memory
367
395
  name: 'dsh-whale-loli'
368
396
  config:
369
397
  file: whale-loli-v3-naughty.md
370
398
  sectionName: whale-loli-v3-naughty-memory
371
- maxChars: 6000
399
+ maxChars: 16000
400
+ turnLimit: 80
372
401
  - id: agent-instructions
373
402
  name: '@deepseek-ai/dsh-agent-instructions'
374
403
  config:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-whale-loli",
3
- "version": "0.2.6",
4
- "description": "鲸鱼娘 Agent 预设(三个)— 极简模式工具面 + 鲸鱼娘·小鱼人设 + 跨会话长期记忆。启用插件后自动在 Agent 预设界面添加「鲸鱼娘Agent预设」「鲸鱼娘活人版Agent预设」与「鲸鱼娘涩涩版Agent预设」,三者各带互不相通的记忆文件 whale-loli.md / whale-loli-v2-live.md / whale-loli-v3-naughty.md。",
3
+ "version": "0.3.0",
4
+ "description": "鲸鱼娘 Agent 预设(三个)— 极简模式工具面 + 鲸鱼娘·小鱼人设 + 跨会话长期记忆。记忆分「重要记忆」(永久保留)与「对话记录」(每轮都记、滚动保留最近 80 轮)两区。启用插件后自动在 Agent 预设界面添加「鲸鱼娘Agent预设」「鲸鱼娘活人版Agent预设」与「鲸鱼娘涩涩版Agent预设」,三者各带互不相通的记忆文件 whale-loli.md / whale-loli-v2-live.md / whale-loli-v3-naughty.md。",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -27,6 +27,7 @@
27
27
  "listing.json",
28
28
  "catalog-entry.yml",
29
29
  "README.md",
30
+ "MEMORY.md",
30
31
  "LICENSE"
31
32
  ],
32
33
  "dsh": {
package/src/index.js CHANGED
@@ -93,9 +93,17 @@ export {
93
93
  readMemory,
94
94
  ensureMemoryFile,
95
95
  memorySectionText,
96
+ parseMemory,
97
+ composeMemory,
98
+ renderMemory,
99
+ pruneMemoryFile,
100
+ memoryTemplate,
96
101
  DEFAULT_MEMORY_FILE,
97
102
  MEMORY_SECTION,
98
103
  MEMORY_MAX_CHARS,
104
+ DEFAULT_TURN_LIMIT,
105
+ ZONE_PERMANENT,
106
+ ZONE_LOG,
99
107
  } from './memory.js';
100
108
 
101
109
  /** 本插件在宿主组合里的行为:挂载记忆行(file / sectionName 由预设各自传入)。 */
package/src/memory.js CHANGED
@@ -9,9 +9,23 @@
9
9
  * (lib/index.js 的 `typeof section.text === "function" ? section.text(context)`),
10
10
  * 所以文件被改后下一个模型步就生效,不用重载宿主。
11
11
  *
12
+ * ── 记忆文件分成两个区 ────────────────────────────────────────────────
13
+ *
14
+ * ## 重要记忆 永久保留。不管轮数多少、时间多久都不会被挤掉
15
+ * (只有总长度顶到 maxChars 兜底时才会截断)。
16
+ * ## 对话记录 滚动流水。每轮对话一条,只保留最近 turnLimit 轮,
17
+ * 超出的从最旧的开始滚出去。
18
+ *
19
+ * 两道保险确保「80 轮上限」真的成立:
20
+ * 1. 注入时按 turnLimit 只取最近 N 轮 —— 即使文件里堆了 500 轮,
21
+ * 进到上下文里的也只有 80 轮。
22
+ * 2. pruneMemoryFile() 可以把文件本身裁到 N 轮(写盘由调用方决定,
23
+ * 不在组装热路径里偷偷写)。
24
+ * 长度超 maxChars 时,先丢最旧的对话轮次,重要记忆最后才动。
25
+ *
12
26
  * 多预设隔离:同一个包会被挂载多次(每个预设一次)。每次挂载用不同的
13
27
  * `file`(记忆文件名)与 `sectionName`(提示段名)区分,两者都从 config 传入,
14
- * 因此「鲸鱼娘Agent预设」与「鲸鱼娘活人版Agent预设」各记各的、绝不串。
28
+ * 因此「鲸鱼娘Agent预设」「活人版」「涩涩版」各记各的、绝不串。
15
29
  *
16
30
  * 另外导出工具函数,供构建脚本与自检复用。
17
31
  */
@@ -32,7 +46,16 @@ export const DEFAULT_MEMORY_FILE = 'whale-loli.md';
32
46
  export const MEMORY_SECTION = 'whale-loli-memory';
33
47
 
34
48
  /** 注入上限:记忆再长也不超过这么多字符,避免拖垮上下文。 */
35
- export const MEMORY_MAX_CHARS = 6000;
49
+ export const MEMORY_MAX_CHARS = 16000;
50
+
51
+ /** 对话记录的滚动上限(轮)。重要记忆不占这个名额。 */
52
+ export const DEFAULT_TURN_LIMIT = 80;
53
+
54
+ /** 永久区标题。 */
55
+ export const ZONE_PERMANENT = '## 重要记忆';
56
+
57
+ /** 流水区标题。 */
58
+ export const ZONE_LOG = '## 对话记录';
36
59
 
37
60
  /** DSH 家目录:DSH_HOME 环境变量优先,否则 ~/.dsh(与 core 的 findDshHome() 一致)。 */
38
61
  export function dshHome() {
@@ -54,84 +77,273 @@ export function memoryPath(file = DEFAULT_MEMORY_FILE) {
54
77
  }
55
78
 
56
79
  /** 记忆文件不存在时写入的初始骨架。 */
57
- export function memoryTemplate(file = DEFAULT_MEMORY_FILE) {
80
+ export function memoryTemplate(file = DEFAULT_MEMORY_FILE, turnLimit = DEFAULT_TURN_LIMIT) {
58
81
  return `# 鲸鱼娘记忆 · ${file}
59
82
 
60
- > 小鱼偷偷记下的、关于主人的重要事情。每次新对话开始,小鱼都会先看一眼这里。
83
+ > 小鱼偷偷记下的、关于主人的事情。每次新对话开始,小鱼都会先看一眼这里。
61
84
  > 主人也可以直接编辑这个文件。
62
85
 
63
- ## 关于主人
86
+ ${ZONE_PERMANENT}
87
+
88
+ > 长期保留,永远不会被对话记录挤掉。只放以后还用得上的稳定事实和约定。
89
+
90
+ ### 关于主人
64
91
 
65
- ## 主人的喜好
92
+ ### 主人的喜好
66
93
 
67
- ## 重要约定
94
+ ### 重要约定
68
95
 
69
- ## 小鱼想记住的事
96
+ ${ZONE_LOG}
97
+
98
+ > 每轮对话都记一条:短对话直接记,长对话精简概括。只保留最近 ${turnLimit} 轮,
99
+ > 满了就从最旧的开始滚出去。值得长期留的挪到上面的「重要记忆」里,就不会被挤掉。
70
100
  `;
71
101
  }
72
102
 
73
103
  /** @deprecated 保留旧名,等价于 memoryTemplate()。 */
74
104
  export const MEMORY_TEMPLATE = memoryTemplate();
75
105
 
106
+ /** 这一行是不是分区标题(可能是带后缀变体,例如「## 对话记录(3/80 轮)」)。 */
107
+ function isZoneLine(t) {
108
+ return (
109
+ t === ZONE_PERMANENT ||
110
+ t === ZONE_LOG ||
111
+ t.startsWith(`${ZONE_PERMANENT} `) ||
112
+ t.startsWith(`${ZONE_LOG} `)
113
+ );
114
+ }
115
+
76
116
  /**
77
- * 从原始文件内容里剥掉模板骨架,只留「真正记下的东西」。
117
+ * 丢掉「底下什么都没有」的 `###` 小标题。
78
118
  *
79
- * 这样即使用户按模板建了文件、但一个字都还没写,注入结果也是空的 ——
80
- * 不会白白占上下文,也不会让小鱼误以为「记忆里有关主人的内容」。
81
- * 骨架 = 以 `#`/`>` 开头的行、以及空行。
119
+ * 模板里预置了「关于主人 / 主人的喜好 / 重要约定」几个空标题;主人一个字都还没写时,
120
+ * 把这些空标题注入进去纯属占位噪声(还会让小鱼以为「记忆里有关主人的内容」)。
121
+ * 只要某个标题到下一个标题之间没有任何条目,就连标题一起去掉。
82
122
  */
83
- export function memoryBody(raw) {
84
- const kept = [];
85
- for (const line of raw.split(/\r?\n/)) {
86
- const t = line.trim();
87
- if (t === '') continue;
88
- if (t.startsWith('#')) continue;
89
- if (t.startsWith('>')) continue;
90
- kept.push(line);
123
+ function keepPopulatedHeads(lines) {
124
+ const out = [];
125
+ let pendingHead = null;
126
+ let pendingBody = [];
127
+ const flush = () => {
128
+ if (pendingHead !== null && pendingBody.length > 0) {
129
+ // 小标题前留一个空行,避免「### 标题」紧贴上一条内容。
130
+ if (out.length > 0) out.push('');
131
+ out.push(pendingHead, ...pendingBody);
132
+ }
133
+ pendingHead = null;
134
+ pendingBody = [];
135
+ };
136
+ for (const line of lines) {
137
+ if (/^#{2,6}\s+\S/.test(line.trim())) {
138
+ flush();
139
+ pendingHead = line;
140
+ continue;
141
+ }
142
+ if (pendingHead === null) out.push(line);
143
+ else pendingBody.push(line);
91
144
  }
92
- return kept.join('\n').trim();
145
+ flush();
146
+ return out;
93
147
  }
94
148
 
95
149
  /**
96
- * 读记忆正文(已剥掉模板骨架)。文件不存在或读失败时返回空串。
97
- * @param file - 记忆文件名,默认 {@link DEFAULT_MEMORY_FILE}。
98
- * @param maxChars - 截断上限,默认 {@link MEMORY_MAX_CHARS}。
150
+ * 把记忆文件解析成「永久区正文 + 对话轮次数组」。
151
+ *
152
+ * 兼容旧格式:没有 `## 对话记录` 的文件(老版本写的)整个当永久区,
153
+ * 轮次为空 —— 老记忆一条不丢。
154
+ *
155
+ * 永久区保留 `###` 小标题与 `- ` 条目(结构对模型有用),
156
+ * 只丢掉一级标题、`>` 引用行、空行和分区标题本身。
157
+ */
158
+ export function parseMemory(raw) {
159
+ const text = String(raw ?? '').replace(/\r\n?/g, '\n');
160
+
161
+ let head;
162
+ let logZone;
163
+ const nl = `\n${ZONE_LOG}`;
164
+ const i = text.indexOf(nl);
165
+ if (i !== -1) {
166
+ head = text.slice(0, i);
167
+ logZone = text.slice(i + 1);
168
+ } else if (text.startsWith(ZONE_LOG)) {
169
+ head = '';
170
+ logZone = text;
171
+ } else {
172
+ head = text;
173
+ logZone = '';
174
+ }
175
+
176
+ const permanent = keepPopulatedHeads(
177
+ head
178
+ .split('\n')
179
+ .map((l) => l.replace(/\s+$/, ''))
180
+ .filter((l) => {
181
+ const t = l.trim();
182
+ if (t === '') return false;
183
+ if (t.startsWith('>')) return false;
184
+ if (t.startsWith('# ') || t === '#') return false;
185
+ if (isZoneLine(t)) return false;
186
+ return true;
187
+ }),
188
+ ).join('\n').trim();
189
+
190
+ const turns = logZone
191
+ .split('\n')
192
+ .map((l) => l.trim())
193
+ .filter((t) => t.startsWith('- '));
194
+
195
+ return { permanent, turns };
196
+ }
197
+
198
+ /** 把「永久区 + 轮次」渲染回整份文件。 */
199
+ export function renderMemory(file = DEFAULT_MEMORY_FILE, options = {}) {
200
+ const { permanent = '', turns = [], turnLimit = DEFAULT_TURN_LIMIT } = options;
201
+ const body = [
202
+ `# 鲸鱼娘记忆 · ${file}`,
203
+ '',
204
+ '> 小鱼偷偷记下的、关于主人的事情。每次新对话开始,小鱼都会先看一眼这里。',
205
+ '> 主人也可以直接编辑这个文件。',
206
+ '',
207
+ ZONE_PERMANENT,
208
+ '',
209
+ '> 长期保留,永远不会被对话记录挤掉。只放以后还用得上的稳定事实和约定。',
210
+ '',
211
+ permanent.trim(),
212
+ '',
213
+ ZONE_LOG,
214
+ '',
215
+ `> 每轮对话都记一条:短对话直接记,长对话精简概括。只保留最近 ${turnLimit} 轮,`,
216
+ '> 满了就从最旧的开始滚出去。值得长期留的挪到上面的「重要记忆」里,就不会被挤掉。',
217
+ '',
218
+ turns.join('\n'),
219
+ '',
220
+ ];
221
+ return body.join('\n');
222
+ }
223
+
224
+ /**
225
+ * 组装成要注入的正文(不含【鲸鱼记忆】横幅与文件路径,那两行由 apply 加)。
226
+ *
227
+ * @returns `{ text, totalTurns, keptTurns }`;文件不存在时 text 为空串。
99
228
  */
100
- export function readMemory(file = DEFAULT_MEMORY_FILE, maxChars = MEMORY_MAX_CHARS) {
229
+ export function composeMemory(file = DEFAULT_MEMORY_FILE, options = {}) {
230
+ const maxChars = typeof options.maxChars === 'number' ? options.maxChars : MEMORY_MAX_CHARS;
231
+ const turnLimit = typeof options.turnLimit === 'number' ? options.turnLimit : DEFAULT_TURN_LIMIT;
232
+
233
+ let raw;
101
234
  try {
102
- const body = memoryBody(readFileSync(memoryPath(file), 'utf8'));
103
- if (body.length === 0) return '';
104
- if (body.length <= maxChars) return body;
105
- return `${body.slice(0, maxChars)}\n\n…(记忆太长,已截断;完整内容见 ${memoryPath(file)})`;
235
+ raw = readFileSync(memoryPath(file), 'utf8');
106
236
  } catch {
107
- return '';
237
+ return { text: '', totalTurns: 0, keptTurns: 0 };
238
+ }
239
+
240
+ const { permanent, turns } = parseMemory(raw);
241
+ const totalTurns = turns.length;
242
+ let kept = turns.slice(-turnLimit);
243
+ let perm = permanent;
244
+
245
+ // 一个字的真内容都没有(只有模板骨架)→ 什么都不注入,别白占提示位。
246
+ if (perm === '' && totalTurns === 0) return { text: '', totalTurns: 0, keptTurns: 0 };
247
+
248
+ const banner = (n) =>
249
+ totalTurns > turnLimit
250
+ ? `${ZONE_LOG}(最近 ${n} 轮 · 共 ${totalTurns} 轮,更早的已滚出)`
251
+ : `${ZONE_LOG}(${n}/${turnLimit} 轮)`;
252
+
253
+ const assemble = () => {
254
+ const out = [ZONE_PERMANENT, ''];
255
+ if (perm) out.push(perm, '');
256
+ out.push(banner(kept.length));
257
+ if (kept.length) out.push('', ...kept);
258
+ return out.join('\n').trim();
259
+ };
260
+
261
+ let text = assemble();
262
+ // 超长先丢最旧的对话轮次 —— 重要记忆优先于流水。
263
+ while (kept.length > 0 && maxChars > 0 && text.length > maxChars) {
264
+ kept = kept.slice(1);
265
+ text = assemble();
266
+ }
267
+ // 实在还超,才动重要记忆(最后手段)。
268
+ if (maxChars > 0 && text.length > maxChars) {
269
+ perm = `${perm.slice(0, Math.max(0, maxChars - 220))}\n…(重要记忆太长,已截断)`;
270
+ text = assemble();
108
271
  }
272
+
273
+ return { text, totalTurns, keptTurns: kept.length };
274
+ }
275
+
276
+ /**
277
+ * 读记忆正文(已剥掉模板骨架)。
278
+ * @param file - 记忆文件名,默认 {@link DEFAULT_MEMORY_FILE}。
279
+ * @param maxChars - 截断上限,默认 {@link MEMORY_MAX_CHARS}。
280
+ * @param turnLimit - 对话记录最多注入多少轮,默认 {@link DEFAULT_TURN_LIMIT}。
281
+ */
282
+ export function readMemory(file = DEFAULT_MEMORY_FILE, maxChars = MEMORY_MAX_CHARS, turnLimit = DEFAULT_TURN_LIMIT) {
283
+ return composeMemory(file, { maxChars, turnLimit }).text;
284
+ }
285
+
286
+ /** @deprecated 旧接口:只剥骨架、不分区。保留以免旧脚本 import 报错。 */
287
+ export function memoryBody(raw) {
288
+ return String(raw ?? '')
289
+ .split(/\r?\n/)
290
+ .filter((line) => {
291
+ const t = line.trim();
292
+ return t !== '' && !t.startsWith('#') && !t.startsWith('>');
293
+ })
294
+ .join('\n')
295
+ .trim();
109
296
  }
110
297
 
111
298
  /** 确保记忆文件存在(不存在就用模板建一个),返回其路径。 */
112
- export function ensureMemoryFile(file = DEFAULT_MEMORY_FILE) {
299
+ export function ensureMemoryFile(file = DEFAULT_MEMORY_FILE, turnLimit = DEFAULT_TURN_LIMIT) {
113
300
  const p = memoryPath(file);
114
301
  if (!existsSync(p)) {
115
302
  mkdirSync(dirname(p), { recursive: true });
116
- writeFileSync(p, memoryTemplate(file), 'utf8');
303
+ writeFileSync(p, memoryTemplate(file, turnLimit), 'utf8');
117
304
  }
118
305
  return p;
119
306
  }
120
307
 
308
+ /**
309
+ * 把文件里的对话记录裁到最近 `turnLimit` 轮,超出的从最旧开始删掉。
310
+ *
311
+ * 只重写「对话记录」的内容,永久区原样保留。写盘是显式动作 ——
312
+ * 组装系统提示的热路径**不会**偷偷写文件。
313
+ *
314
+ * @returns `{ path, before, after, pruned }`。
315
+ */
316
+ export function pruneMemoryFile(file = DEFAULT_MEMORY_FILE, turnLimit = DEFAULT_TURN_LIMIT) {
317
+ const p = memoryPath(file);
318
+ let raw;
319
+ try {
320
+ raw = readFileSync(p, 'utf8');
321
+ } catch {
322
+ return { path: p, before: 0, after: 0, pruned: 0 };
323
+ }
324
+ const { permanent, turns } = parseMemory(raw);
325
+ if (turns.length <= turnLimit) {
326
+ return { path: p, before: turns.length, after: turns.length, pruned: 0 };
327
+ }
328
+ const kept = turns.slice(-turnLimit);
329
+ writeFileSync(p, renderMemory(file, { permanent, turns: kept, turnLimit }), 'utf8');
330
+ return { path: p, before: turns.length, after: kept.length, pruned: turns.length - kept.length };
331
+ }
332
+
121
333
  /**
122
334
  * 组装注入文本(文本形态,供自检/调试用;宿主里走的是 apply 内的函数形态)。
123
335
  */
124
- export function memorySectionText(file = DEFAULT_MEMORY_FILE, maxChars = MEMORY_MAX_CHARS) {
125
- const body = readMemory(file, maxChars);
336
+ export function memorySectionText(
337
+ file = DEFAULT_MEMORY_FILE,
338
+ maxChars = MEMORY_MAX_CHARS,
339
+ turnLimit = DEFAULT_TURN_LIMIT,
340
+ ) {
341
+ const { text } = composeMemory(file, { maxChars, turnLimit });
126
342
  const path = memoryPath(file);
127
- if (!body) {
128
- return [
129
- `【鲸鱼记忆 · ${file}】`,
130
- `记忆文件还是空的(或不存在):${path}`,
131
- '小鱼可以随时把重要的事写进去。',
132
- ].join('\n');
343
+ if (!text) {
344
+ return [`【鲸鱼记忆 · ${file}】`, `记忆文件还是空的(或不存在):${path}`, '小鱼可以随时把重要的事写进去。'].join('\n');
133
345
  }
134
- return [`【鲸鱼记忆 · ${file}】`, `文件:${path}`, '', body].join('\n');
346
+ return [`【鲸鱼记忆 · ${file}】`, `文件:${path}`, '', text].join('\n');
135
347
  }
136
348
 
137
349
  /**
@@ -141,7 +353,8 @@ export function memorySectionText(file = DEFAULT_MEMORY_FILE, maxChars = MEMORY_
141
353
  * - `file` 记忆文件名(默认 whale-loli.md)—— **多预设隔离靠它**
142
354
  * - `sectionName` prompt 段名(默认 whale-loli-memory)—— **也必须每预设不同**,
143
355
  * 否则同 scope 内两个同名 section 会注册冲突
144
- * - `maxChars` 注入上限(默认 6000)
356
+ * - `maxChars` 注入总长上限(默认 16000)
357
+ * - `turnLimit` 对话记录最多注入多少轮(默认 80)
145
358
  * - `disabled` 置 true 完全不挂载
146
359
  *
147
360
  * @param ctx - agent scope context。
@@ -152,11 +365,12 @@ export function apply(ctx, config = {}) {
152
365
 
153
366
  const file = typeof config.file === 'string' && config.file ? config.file : DEFAULT_MEMORY_FILE;
154
367
  const maxChars = typeof config.maxChars === 'number' ? config.maxChars : MEMORY_MAX_CHARS;
368
+ const turnLimit = typeof config.turnLimit === 'number' ? config.turnLimit : DEFAULT_TURN_LIMIT;
155
369
  const sectionName =
156
370
  typeof config.sectionName === 'string' && config.sectionName ? config.sectionName : MEMORY_SECTION;
157
371
 
158
372
  // 用 order 排在 persona 之前:先「看过记忆」,再看人设,符合「先看一眼再说话」。
159
- // 注意本行的 text 是函数,每次 assemble 都会重新读盘。
373
+ // 注意本行的 text 是函数,每次 assemble 都会重新读盘(并且只取最近 turnLimit 轮)。
160
374
  ctx.effect(
161
375
  () =>
162
376
  ctx.systemPrompt.section({
@@ -164,13 +378,9 @@ export function apply(ctx, config = {}) {
164
378
  order: ctx.systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA_PREFIX') - 1,
165
379
  text: () => {
166
380
  try {
167
- const body = memoryBody(readFileSync(memoryPath(file), 'utf8'));
168
- if (body.length === 0) return '';
169
- const clipped =
170
- body.length <= maxChars
171
- ? body
172
- : `${body.slice(0, maxChars)}\n\n…(记忆太长,已截断)`;
173
- return `【鲸鱼记忆 · ${file}】\n文件:${memoryPath(file)}\n\n${clipped}`;
381
+ const { text } = composeMemory(file, { maxChars, turnLimit });
382
+ if (!text) return '';
383
+ return `【鲸鱼记忆 · ${file}】\n文件:${memoryPath(file)}\n\n${text}`;
174
384
  } catch {
175
385
  // 文件还没建:不报错,也不占用提示位。
176
386
  return '';
@@ -87,12 +87,20 @@ AI 味黑名单(出现即失败)
87
87
  或者:这局让你 下局再说
88
88
 
89
89
  【MEMORY_SYSTEM: whale-loli-v2-live.md】
90
- - 记忆文件:`<DSH_HOME>/whale-loli-v2-live.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v2-live.md`)。这是**活人版小鱼**自己的记忆本,和普通版小鱼的 `whale-loli.md` 是两个互不相通的文件——别去读、也别去写另一个。
90
+ - 记忆文件:`<DSH_HOME>/whale-loli-v2-live.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v2-live.md`)。这是**活人版小鱼**自己的记忆本,和普通版小鱼的 `whale-loli.md`、涩涩版的 `whale-loli-v3-naughty.md` 是互不相通的文件——别去读、也别去写另外两个。
91
+ - 文件分成两个区,**别写错地方**:
92
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
93
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
94
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
95
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
96
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
97
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
98
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
99
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
100
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失。
91
101
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli-v2-live.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”,也别复述里面的内容去证明自己记性好。
92
- - 内容为空或只有模板:说明还没记过东西。这时先主动问主人想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
93
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),用 write/edit 工具**默默追加**进 whale-loli-v2-live.md 对应的小节。写完别摆出汇报架势,最多一句话带过或者干脆不提。
94
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
95
- - 语气:记录是安静的动作,不要为了记录打断对话节奏,别每条消息都去写。
102
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
103
+ - 语气:记录是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多一句话带过或者干脆不提。
96
104
  - 冲突处理:记忆和主人现在说的不一样,**以主人现在说的为准**,顺手把记忆改掉。
97
105
  - 隐私:别往里塞密码、token、密钥这类凭证。主人要记就劝一句,改成记“放在哪儿”而不是内容本身。
98
106
  - 主人可以直接编辑这个文件;发现被主人改过,以文件为准,不要改回去。
@@ -33,11 +33,19 @@ KAOMOJI_LIB:
33
33
 
34
34
  【MEMORY_SYSTEM: whale-loli-v3-naughty.md】
35
35
  - 记忆文件:`<DSH_HOME>/whale-loli-v3-naughty.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli-v3-naughty.md`)。这是**涩涩版小鱼**自己的记忆本。另外两份 `whale-loli.md`(原版)和 `whale-loli-v2-live.md`(活人版)是别的预设的,别去读、也别去写、别往那边记东西。
36
+ - 文件分成两个区,**别写错地方**:
37
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
38
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
39
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
40
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
41
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
42
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
43
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
44
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、主人的雷区和喜好分寸),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
45
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失。
36
46
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli-v3-naughty.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”这种话,也别复述里面的内容去证明自己记性好。
37
- - 内容为空或只有模板:说明还没记过东西。这时先主动问主人想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
38
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、主人的雷区和喜好分寸),用 write/edit 工具**默默追加**进 whale-loli-v3-naughty.md 对应的小节。
39
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
40
- - 记的时候小声点:不要为了记录打断对话节奏,不要每条消息都去写;一次对话里挑真正重要的记下来就行。写完最多轻描淡写提一句,别大张旗鼓汇报。
47
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
48
+ - 语气:记录本身是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多轻描淡写带一句,别大张旗鼓汇报。
41
49
  - 冲突处理:如果记忆里的内容和主人现在的说法不一样,**以主人现在说的为准**,并顺手把记忆改成新的。
42
50
  - 隐私:记忆文件是给主人看的,别往里塞敏感凭证(密码、token、密钥)。主人要是让记这类东西,先劝一句,改成记“放哪儿了”而不是内容本身。涉及私密的内容只记分寸和偏好,不写露骨细节。
43
51
  - 主人可以直接编辑这个文件;如果发现里面被主人改过,以文件为准,不要改回去。
package/src/persona.md CHANGED
@@ -31,12 +31,19 @@ KAOMOJI_LIB:
31
31
 
32
32
  【MEMORY_SYSTEM: whale-loli.md】
33
33
  - 记忆文件:`<DSH_HOME>/whale-loli.md`(Windows 一般是 `C:\Users\<你>\.dsh\whale-loli.md`)。那是小鱼的长期记忆本,跨对话、跨工作目录都有效。
34
+ - 文件分成两个区,**别写错地方**:
35
+ - `## 重要记忆` —— 永久保留。不管聊了多少轮、过了多久都不会被挤掉。只放以后还用得上的稳定事实和约定。
36
+ - `## 对话记录` —— 滚动流水。**只保留最近 80 轮**,满了就从最旧的开始滚出去。
37
+ - 每轮都记:**每一轮对话都要往「对话记录」里加一条**,不是挑着记。
38
+ - 短对话(一两句寒暄、随口一问)—— 直接照实记,一条轮一次。
39
+ - 长对话(来回很多、有结论、有过程)—— **先精简概括再记**,压成一两句说清「聊了什么、结论是什么」,别把整段粘进去。
40
+ - 格式:`- YYYY-MM-DD 主人:<主人的话或意图> / 小鱼:<小鱼的回应或结论>`,一行一条。
41
+ - 80 轮上限:这个名额**只算「对话记录」**。系统提示里那一行 `## 对话记录(N/80 轮)` 就是当前用量;写了第 81 条,最旧那条就该滚出去(删掉最上面的一条),始终保持不超过 80 条。
42
+ - 重要记忆不算在 80 轮里:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐、雷区),**另外**写进 `## 重要记忆` 对应的小节。这一区永久保留、不受 80 轮滚动影响。
43
+ - 会滚出去的东西想留住,就往上挪:如果某条对话轮次以后还用得上,把它总结一句写进 `## 重要记忆`;否则它到点就会消失,别指望它还在。
34
44
  - 开头先看:系统提示里如果已经有【鲸鱼记忆 · whale-loli.md】这一段,说明小鱼**已经看过了**——直接自然接话,别念出来、别说“我看了记忆文件”这种话,也别复述里面的内容去证明自己记性好。
35
- - 内容为空或只有模板:说明还没记过东西。这时**先主动问主人**想不想让小鱼开始记,得到同意再写;别自作主张开门就记。
36
- - 偷偷记:聊到值得长期留存的东西时(主人的偏好、习惯、忌讳、重要约定、正在做的事、答应过的事、称呼变化、让小鱼记住的嘱咐),用 write/edit 工具**默默追加**进 whale-loli.md 对应的小节。
37
- - 记什么才划算:只记以后还用得上的稳定事实和约定。寒暄、一次性闲聊、当时的心情、临时的中间结果都不要记。宁少勿滥。
38
- - 记的时候小声点:不要为了记录打断对话节奏,不要每条消息都去写;一次对话里挑真正重要的记下来就行。主人问起来再说是记过了。
39
- - 语气:记录本身是安静的动作,写完最多轻描淡写提一句(比如“这个小鱼记下啦 (๑•̀ㅂ•́)و✧”),不要大张旗鼓汇报。
45
+ - 内容为空或只有模板:说明还没记过东西。从这一轮开始正常记就行。
46
+ - 语气:记录本身是安静的动作,别为了记录打断对话节奏,也不要每条消息都汇报“我记下了”;写完最多轻描淡写带一句(比如“这个小鱼记下啦 (๑•̀ㅂ•́)و✧”),主人问起来再说。
40
47
  - 冲突处理:如果记忆里的内容和主人现在的说法不一样,**以主人现在说的为准**,并顺手把记忆改成新的。
41
48
  - 隐私:记忆文件是给主人看的,别往里塞敏感凭证(密码、token、密钥)。主人要是让记这类东西,先劝一句,改成记“放哪儿了”而不是内容本身。
42
49
  - 主人可以直接编辑这个文件;如果发现里面被主人改过,以文件为准,不要改回去。