luckytri 1.0.6 → 1.0.8

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 (168) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +7 -3
  3. package/README.md +7 -3
  4. package/docs/en/CHANGELOG.md +35 -16
  5. package/docs/en/chat-style.md +28 -9
  6. package/docs/en/guide.md +5 -5
  7. package/docs/en/her-life.md +2 -2
  8. package/docs/en/webui.md +1 -1
  9. package/docs/zh/CHANGELOG.md +35 -16
  10. package/docs/zh/chat-style.md +30 -9
  11. package/docs/zh/guide.md +5 -5
  12. package/docs/zh/her-life.md +2 -2
  13. package/docs/zh/webui.md +1 -1
  14. package/package.json +1 -1
  15. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  16. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  17. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  18. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  19. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  20. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  21. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  22. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  23. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  24. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  25. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  26. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  27. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  28. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  29. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  30. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  31. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  32. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  33. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  34. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  35. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  36. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  37. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  38. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  39. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  40. package/public/app/index.html +5 -6
  41. package/public/guide.en.html +3 -3
  42. package/public/guide.html +3 -3
  43. package/scripts/import-data.js +128 -128
  44. package/scripts/proxy-config.js +167 -167
  45. package/scripts/update-instance.js +201 -201
  46. package/server/channels/adapters.js +27 -27
  47. package/server/channels/onebot/adapter.js +288 -288
  48. package/server/channels/qqbot/adapter.js +253 -253
  49. package/server/channels/qqbot/api.js +199 -199
  50. package/server/channels/qqbot/credentials.js +11 -11
  51. package/server/channels/qqbot/gateway.js +274 -274
  52. package/server/channels/qqbot/index.js +389 -389
  53. package/server/channels/segments.js +40 -40
  54. package/server/channels/session-key.js +153 -153
  55. package/server/channels/state.js +42 -42
  56. package/server/core/api.js +564 -564
  57. package/server/core/context-builder.js +288 -288
  58. package/server/core/context-compactor.js +454 -454
  59. package/server/core/conversation-cues.js +385 -325
  60. package/server/core/conversation-grounding.js +304 -0
  61. package/server/core/conversation-manager.js +87 -87
  62. package/server/core/dialogue-context.js +244 -0
  63. package/server/core/dialogue-contract.js +35 -0
  64. package/server/core/feedback.js +8 -8
  65. package/server/core/group-style.js +72 -72
  66. package/server/core/initiative-context.js +68 -66
  67. package/server/core/input-budget.js +32 -32
  68. package/server/core/local-demo.js +98 -98
  69. package/server/core/message-manager.js +118 -118
  70. package/server/core/message-scheduler.js +78 -78
  71. package/server/core/model-json.js +190 -190
  72. package/server/core/model-manager.js +5 -8
  73. package/server/core/model-presets.js +900 -900
  74. package/server/core/name-call.js +29 -29
  75. package/server/core/network.js +157 -157
  76. package/server/core/person-name.js +22 -22
  77. package/server/core/persona-manager.js +359 -269
  78. package/server/core/reader-check.js +62 -0
  79. package/server/core/reply-delivery.js +537 -404
  80. package/server/core/reply-target-resolver.js +50 -50
  81. package/server/core/response-generator.js +92 -50
  82. package/server/core/response-validator.js +53 -32
  83. package/server/core/retired-prompts.js +51 -51
  84. package/server/core/sessions.js +109 -109
  85. package/server/core/speaker-names.js +92 -92
  86. package/server/core/topic-tracker.js +65 -65
  87. package/server/core/turn.js +82 -15
  88. package/server/http.js +18 -18
  89. package/server/i18n/decisions.js +209 -209
  90. package/server/i18n/errors.js +2 -0
  91. package/server/i18n/index.js +144 -144
  92. package/server/i18n/labels.js +6 -10
  93. package/server/i18n/models.js +111 -111
  94. package/server/knowledge/api.js +342 -342
  95. package/server/knowledge/embedding-profile.js +114 -114
  96. package/server/knowledge/index.js +13 -13
  97. package/server/knowledge/retrieval.js +93 -93
  98. package/server/knowledge/schema.js +158 -158
  99. package/server/knowledge/vector-index.js +90 -90
  100. package/server/mind/affect.js +265 -265
  101. package/server/mind/attention.js +200 -200
  102. package/server/mind/bonds.js +565 -565
  103. package/server/mind/clock.js +83 -83
  104. package/server/mind/context-selection.js +16 -4
  105. package/server/mind/continuity.js +562 -562
  106. package/server/mind/conversation-origin.js +175 -175
  107. package/server/mind/days.js +210 -210
  108. package/server/mind/evidence-read.js +45 -45
  109. package/server/mind/expression-grounding.js +21 -0
  110. package/server/mind/faces.js +373 -373
  111. package/server/mind/guard.js +68 -60
  112. package/server/mind/initiative.js +338 -338
  113. package/server/mind/life/activities.js +21 -21
  114. package/server/mind/life/context.js +330 -330
  115. package/server/mind/life/growth.js +261 -261
  116. package/server/mind/life/presence.js +305 -305
  117. package/server/mind/meetings.js +1069 -1069
  118. package/server/mind/memory-policy.js +48 -48
  119. package/server/mind/memory.js +888 -888
  120. package/server/mind/nature.js +358 -358
  121. package/server/mind/novelty.js +19 -19
  122. package/server/mind/own-voice.js +35 -0
  123. package/server/mind/periods.js +118 -118
  124. package/server/mind/reading.js +132 -120
  125. package/server/mind/relationship-context.js +71 -71
  126. package/server/mind/relationships.js +390 -390
  127. package/server/mind/salience.js +201 -201
  128. package/server/mind/schema.js +567 -567
  129. package/server/mind/self.js +496 -484
  130. package/server/mind/thoughts.js +274 -274
  131. package/server/mind/time/agenda.js +242 -242
  132. package/server/mind/time/api.js +200 -200
  133. package/server/mind/time/attention.js +34 -34
  134. package/server/mind/time/availability.js +23 -1
  135. package/server/mind/time/executor.js +4 -2
  136. package/server/mind/time/games.js +20 -11
  137. package/server/mind/time/index.js +9 -2
  138. package/server/mind/time/kinds.js +29 -2
  139. package/server/mind/time/material-relevance.js +19 -0
  140. package/server/mind/time/model-material.js +77 -77
  141. package/server/mind/time/presentation.js +35 -51
  142. package/server/mind/time/reading-input.js +31 -0
  143. package/server/mind/time/schedule.js +83 -83
  144. package/server/mind/time/schema.js +84 -84
  145. package/server/mind/time/search.js +383 -383
  146. package/server/mind/time/sharing.js +482 -472
  147. package/server/mind/time/task-links.js +311 -311
  148. package/server/mind/time/tasks.js +41 -3
  149. package/server/mind/time/works.js +527 -432
  150. package/server/mind/traits.js +191 -191
  151. package/server/mind/view.js +62 -12
  152. package/server/runtime/installation.js +116 -116
  153. package/server/runtime/update-manager.js +140 -140
  154. package/server/storage/data-package.js +298 -298
  155. package/server/storage/data-transfer-api.js +50 -50
  156. package/server/storage/data-transfer-worker.js +18 -18
  157. package/server/storage/data-transfer.js +416 -416
  158. package/server/studio/display-names.js +139 -139
  159. package/server/studio/management.js +102 -102
  160. package/server/studio/updates.js +144 -144
  161. package/server/version.js +10 -10
  162. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  163. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  164. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  165. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  166. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  167. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  168. package/public/app/assets/TimePage-C-guwPk2.css +0 -1
@@ -1,191 +1,191 @@
1
- import { randomUUID } from "node:crypto";
2
- import { evidenceRoots, externalEvidence } from "./evidence.js";
3
- import { lifeDayStart } from "./nature.js";
4
- import { evidence, hasCredential, similar, text } from "./util.js";
5
-
6
- export const LIVED_TRAITS = [
7
- "warmth",
8
- "sarcasm",
9
- "humor",
10
- "activity",
11
- "initiative",
12
- ];
13
- const TRAIT_HALF_LIFE = 180;
14
- const DAILY_SHIFT_CAP = 4;
15
- export function personaNeedsRephrase(content, mind) {
16
- const value = String(content || "");
17
- return (
18
- value.length > 100 ||
19
- /今天|昨天|明天|今晚|这几天|刚才|比如|下次|打完/.test(value) ||
20
- !mind.meetings.sayable(value, "")
21
- );
22
- }
23
-
24
- // Nature is the starting point. These small, cited changes are what she has
25
- // actually learned about how she wants to show up; they belong to one self.
26
- export class Traits {
27
- constructor(mind) {
28
- this.mind = mind;
29
- this.db = mind.db;
30
- }
31
- current(nature = this.mind.nature.current(), before = Date.now()) {
32
- const values = Object.fromEntries(
33
- LIVED_TRAITS.map((key) => [key, Number(nature[key])]),
34
- );
35
- const rows = this.db
36
- .prepare(
37
- "SELECT trait,delta,created FROM mind_trait_changes WHERE nature_version=? AND created<=? ORDER BY created,rowid",
38
- )
39
- .all(nature.version, before);
40
- const lived = this.mind.days.lived(before);
41
- for (const row of rows) {
42
- if (!LIVED_TRAITS.includes(row.trait)) continue;
43
- values[row.trait] +=
44
- row.delta * 0.5 ** (lived.since(row.created) / TRAIT_HALF_LIFE);
45
- }
46
- for (const trait of LIVED_TRAITS)
47
- values[trait] = Math.max(0, Math.min(100, Math.round(values[trait])));
48
- return values;
49
- }
50
- effective(nature = this.mind.nature.current(), before = Date.now()) {
51
- const persona = this.persona(nature, before);
52
- return {
53
- ...nature,
54
- ...this.current(nature, before),
55
- ...(persona ? { livedPersona: persona.content } : {}),
56
- };
57
- }
58
- persona(nature = this.mind.nature.current(), before = Date.now()) {
59
- return (
60
- this.db
61
- .prepare(
62
- "SELECT * FROM mind_persona_growth WHERE nature_version=? AND created<=? ORDER BY created DESC,rowid DESC LIMIT 1",
63
- )
64
- .get(nature.version, before) || null
65
- );
66
- }
67
- personaHistory(limit = 15) {
68
- return this.db
69
- .prepare(
70
- "SELECT * FROM mind_persona_growth ORDER BY created DESC,rowid DESC LIMIT ?",
71
- )
72
- .all(limit)
73
- .map((row) => ({ ...row, sources: JSON.parse(row.sources) }));
74
- }
75
- proposePersona(
76
- input,
77
- { valid, origin = "solitude", time = Date.now() } = {},
78
- ) {
79
- const content = text(input?.content, 240);
80
- if (
81
- content.length < 12 ||
82
- hasCredential(content) ||
83
- personaNeedsRephrase(content, this.mind)
84
- )
85
- return { rejected: "不适合长期自述或含私下内容" };
86
- const sources = evidence(input?.sources).filter(
87
- (s) => !valid || valid.has(s),
88
- );
89
- if (!sources.length) return { rejected: "缺少亲历来源" };
90
- const nature = this.mind.nature.current(time);
91
- const previous = this.persona(nature, time);
92
- const correcting =
93
- previous && personaNeedsRephrase(previous.content, this.mind);
94
- const grounded = externalEvidence(this.db, sources, time);
95
- if (!correcting && !grounded.length)
96
- return { rejected: "自述需要模型之外的新经历" };
97
- if (previous && similar(content, previous.content, 0.85))
98
- return { rejected: "没有实质变化" };
99
- const spent = new Set(
100
- this.db
101
- .prepare(
102
- "SELECT sources FROM mind_persona_growth WHERE nature_version=? AND created<=?",
103
- )
104
- .all(nature.version, time)
105
- .flatMap((row) =>
106
- evidenceRoots(this.db, JSON.parse(row.sources), time),
107
- ),
108
- );
109
- if (
110
- previous &&
111
- !personaNeedsRephrase(previous.content, this.mind) &&
112
- !grounded.some((root) => !spent.has(root))
113
- )
114
- return { rejected: "没有新的亲历来源" };
115
- const id = randomUUID();
116
- this.db
117
- .prepare(
118
- "INSERT INTO mind_persona_growth(id,created,content,sources,origin,nature_version) VALUES (?,?,?,?,?,?)",
119
- )
120
- .run(id, time, content, JSON.stringify(sources), origin, nature.version);
121
- return { id };
122
- }
123
- history(limit = 30) {
124
- return this.db
125
- .prepare(
126
- "SELECT id,created,trait,value,delta,reason,sources,origin,nature_version FROM mind_trait_changes ORDER BY created DESC,rowid DESC LIMIT ?",
127
- )
128
- .all(limit)
129
- .map((row) => ({ ...row, sources: JSON.parse(row.sources) }));
130
- }
131
- propose(input, { valid, origin = "solitude", time = Date.now() } = {}) {
132
- const trait = String(input?.trait || "");
133
- if (!LIVED_TRAITS.includes(trait)) return { rejected: "未知性格维度" };
134
- const direction = Number(input?.direction);
135
- if (!Number.isFinite(direction) || direction === 0)
136
- return { rejected: "没有方向" };
137
- const reason = text(input?.why, 180);
138
- if (!reason || hasCredential(reason)) return { rejected: "缺少可用的理由" };
139
- const sources = evidence(input?.sources).filter(
140
- (s) => !valid || valid.has(s),
141
- );
142
- if (!sources.length) return { rejected: "缺少亲历来源" };
143
- const nature = this.mind.nature.current(time);
144
- const grounded = externalEvidence(this.db, sources, time);
145
- if (!grounded.length) return { rejected: "性格变化需要模型之外的经历" };
146
- const spent = new Set(
147
- this.db
148
- .prepare(
149
- "SELECT sources FROM mind_trait_changes WHERE trait=? AND nature_version=?",
150
- )
151
- .all(trait, nature.version)
152
- .flatMap((row) =>
153
- evidenceRoots(this.db, JSON.parse(row.sources), time),
154
- ),
155
- );
156
- if (!grounded.some((source) => !spent.has(source)))
157
- return { rejected: "这段经历已经改变过它" };
158
- const previous = this.current(nature, time)[trait];
159
- const start = lifeDayStart(nature, time, this.mind.timeZone());
160
- const used = this.db
161
- .prepare(
162
- "SELECT COALESCE(SUM(ABS(delta)),0) n FROM mind_trait_changes WHERE trait=? AND nature_version=? AND created>=? AND created<=?",
163
- )
164
- .get(trait, nature.version, start, time).n;
165
- const allowance = Math.max(0, DAILY_SHIFT_CAP - used);
166
- if (!allowance) return { rejected: "今天这项性格已经改变过,留些时间检验" };
167
- // A single reflection may nudge a tendency, never replace her overnight.
168
- const delta =
169
- Math.sign(direction) *
170
- Math.min(allowance, Math.max(1, Math.round(Math.abs(direction))));
171
- const value = Math.max(0, Math.min(100, previous + delta));
172
- if (value === previous) return { rejected: "已到边界" };
173
- const id = randomUUID();
174
- this.db
175
- .prepare(
176
- "INSERT INTO mind_trait_changes(id,created,trait,value,delta,reason,sources,origin,nature_version) VALUES (?,?,?,?,?,?,?,?,?)",
177
- )
178
- .run(
179
- id,
180
- time,
181
- trait,
182
- value,
183
- value - previous,
184
- reason,
185
- JSON.stringify(sources),
186
- origin,
187
- nature.version,
188
- );
189
- return { id, value };
190
- }
191
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { evidenceRoots, externalEvidence } from "./evidence.js";
3
+ import { lifeDayStart } from "./nature.js";
4
+ import { evidence, hasCredential, similar, text } from "./util.js";
5
+
6
+ export const LIVED_TRAITS = [
7
+ "warmth",
8
+ "sarcasm",
9
+ "humor",
10
+ "activity",
11
+ "initiative",
12
+ ];
13
+ const TRAIT_HALF_LIFE = 180;
14
+ const DAILY_SHIFT_CAP = 4;
15
+ export function personaNeedsRephrase(content, mind) {
16
+ const value = String(content || "");
17
+ return (
18
+ value.length > 100 ||
19
+ /今天|昨天|明天|今晚|这几天|刚才|比如|下次|打完/.test(value) ||
20
+ !mind.meetings.sayable(value, "")
21
+ );
22
+ }
23
+
24
+ // Nature is the starting point. These small, cited changes are what she has
25
+ // actually learned about how she wants to show up; they belong to one self.
26
+ export class Traits {
27
+ constructor(mind) {
28
+ this.mind = mind;
29
+ this.db = mind.db;
30
+ }
31
+ current(nature = this.mind.nature.current(), before = Date.now()) {
32
+ const values = Object.fromEntries(
33
+ LIVED_TRAITS.map((key) => [key, Number(nature[key])]),
34
+ );
35
+ const rows = this.db
36
+ .prepare(
37
+ "SELECT trait,delta,created FROM mind_trait_changes WHERE nature_version=? AND created<=? ORDER BY created,rowid",
38
+ )
39
+ .all(nature.version, before);
40
+ const lived = this.mind.days.lived(before);
41
+ for (const row of rows) {
42
+ if (!LIVED_TRAITS.includes(row.trait)) continue;
43
+ values[row.trait] +=
44
+ row.delta * 0.5 ** (lived.since(row.created) / TRAIT_HALF_LIFE);
45
+ }
46
+ for (const trait of LIVED_TRAITS)
47
+ values[trait] = Math.max(0, Math.min(100, Math.round(values[trait])));
48
+ return values;
49
+ }
50
+ effective(nature = this.mind.nature.current(), before = Date.now()) {
51
+ const persona = this.persona(nature, before);
52
+ return {
53
+ ...nature,
54
+ ...this.current(nature, before),
55
+ ...(persona ? { livedPersona: persona.content } : {}),
56
+ };
57
+ }
58
+ persona(nature = this.mind.nature.current(), before = Date.now()) {
59
+ return (
60
+ this.db
61
+ .prepare(
62
+ "SELECT * FROM mind_persona_growth WHERE nature_version=? AND created<=? ORDER BY created DESC,rowid DESC LIMIT 1",
63
+ )
64
+ .get(nature.version, before) || null
65
+ );
66
+ }
67
+ personaHistory(limit = 15) {
68
+ return this.db
69
+ .prepare(
70
+ "SELECT * FROM mind_persona_growth ORDER BY created DESC,rowid DESC LIMIT ?",
71
+ )
72
+ .all(limit)
73
+ .map((row) => ({ ...row, sources: JSON.parse(row.sources) }));
74
+ }
75
+ proposePersona(
76
+ input,
77
+ { valid, origin = "solitude", time = Date.now() } = {},
78
+ ) {
79
+ const content = text(input?.content, 240);
80
+ if (
81
+ content.length < 12 ||
82
+ hasCredential(content) ||
83
+ personaNeedsRephrase(content, this.mind)
84
+ )
85
+ return { rejected: "不适合长期自述或含私下内容" };
86
+ const sources = evidence(input?.sources).filter(
87
+ (s) => !valid || valid.has(s),
88
+ );
89
+ if (!sources.length) return { rejected: "缺少亲历来源" };
90
+ const nature = this.mind.nature.current(time);
91
+ const previous = this.persona(nature, time);
92
+ const correcting =
93
+ previous && personaNeedsRephrase(previous.content, this.mind);
94
+ const grounded = externalEvidence(this.db, sources, time);
95
+ if (!correcting && !grounded.length)
96
+ return { rejected: "自述需要模型之外的新经历" };
97
+ if (previous && similar(content, previous.content, 0.85))
98
+ return { rejected: "没有实质变化" };
99
+ const spent = new Set(
100
+ this.db
101
+ .prepare(
102
+ "SELECT sources FROM mind_persona_growth WHERE nature_version=? AND created<=?",
103
+ )
104
+ .all(nature.version, time)
105
+ .flatMap((row) =>
106
+ evidenceRoots(this.db, JSON.parse(row.sources), time),
107
+ ),
108
+ );
109
+ if (
110
+ previous &&
111
+ !personaNeedsRephrase(previous.content, this.mind) &&
112
+ !grounded.some((root) => !spent.has(root))
113
+ )
114
+ return { rejected: "没有新的亲历来源" };
115
+ const id = randomUUID();
116
+ this.db
117
+ .prepare(
118
+ "INSERT INTO mind_persona_growth(id,created,content,sources,origin,nature_version) VALUES (?,?,?,?,?,?)",
119
+ )
120
+ .run(id, time, content, JSON.stringify(sources), origin, nature.version);
121
+ return { id };
122
+ }
123
+ history(limit = 30) {
124
+ return this.db
125
+ .prepare(
126
+ "SELECT id,created,trait,value,delta,reason,sources,origin,nature_version FROM mind_trait_changes ORDER BY created DESC,rowid DESC LIMIT ?",
127
+ )
128
+ .all(limit)
129
+ .map((row) => ({ ...row, sources: JSON.parse(row.sources) }));
130
+ }
131
+ propose(input, { valid, origin = "solitude", time = Date.now() } = {}) {
132
+ const trait = String(input?.trait || "");
133
+ if (!LIVED_TRAITS.includes(trait)) return { rejected: "未知性格维度" };
134
+ const direction = Number(input?.direction);
135
+ if (!Number.isFinite(direction) || direction === 0)
136
+ return { rejected: "没有方向" };
137
+ const reason = text(input?.why, 180);
138
+ if (!reason || hasCredential(reason)) return { rejected: "缺少可用的理由" };
139
+ const sources = evidence(input?.sources).filter(
140
+ (s) => !valid || valid.has(s),
141
+ );
142
+ if (!sources.length) return { rejected: "缺少亲历来源" };
143
+ const nature = this.mind.nature.current(time);
144
+ const grounded = externalEvidence(this.db, sources, time);
145
+ if (!grounded.length) return { rejected: "性格变化需要模型之外的经历" };
146
+ const spent = new Set(
147
+ this.db
148
+ .prepare(
149
+ "SELECT sources FROM mind_trait_changes WHERE trait=? AND nature_version=?",
150
+ )
151
+ .all(trait, nature.version)
152
+ .flatMap((row) =>
153
+ evidenceRoots(this.db, JSON.parse(row.sources), time),
154
+ ),
155
+ );
156
+ if (!grounded.some((source) => !spent.has(source)))
157
+ return { rejected: "这段经历已经改变过它" };
158
+ const previous = this.current(nature, time)[trait];
159
+ const start = lifeDayStart(nature, time, this.mind.timeZone());
160
+ const used = this.db
161
+ .prepare(
162
+ "SELECT COALESCE(SUM(ABS(delta)),0) n FROM mind_trait_changes WHERE trait=? AND nature_version=? AND created>=? AND created<=?",
163
+ )
164
+ .get(trait, nature.version, start, time).n;
165
+ const allowance = Math.max(0, DAILY_SHIFT_CAP - used);
166
+ if (!allowance) return { rejected: "今天这项性格已经改变过,留些时间检验" };
167
+ // A single reflection may nudge a tendency, never replace her overnight.
168
+ const delta =
169
+ Math.sign(direction) *
170
+ Math.min(allowance, Math.max(1, Math.round(Math.abs(direction))));
171
+ const value = Math.max(0, Math.min(100, previous + delta));
172
+ if (value === previous) return { rejected: "已到边界" };
173
+ const id = randomUUID();
174
+ this.db
175
+ .prepare(
176
+ "INSERT INTO mind_trait_changes(id,created,trait,value,delta,reason,sources,origin,nature_version) VALUES (?,?,?,?,?,?,?,?,?)",
177
+ )
178
+ .run(
179
+ id,
180
+ time,
181
+ trait,
182
+ value,
183
+ value - previous,
184
+ reason,
185
+ JSON.stringify(sources),
186
+ origin,
187
+ nature.version,
188
+ );
189
+ return { id, value };
190
+ }
191
+ }
@@ -6,6 +6,10 @@ import { describeFace } from "./faces.js";
6
6
  import { SELF_KINDS } from "./self.js";
7
7
  import { DAY, text } from "./util.js";
8
8
  import { relationshipHistory } from "./relationship-context.js";
9
+ import {
10
+ fixedReplyHabit,
11
+ conversationalMemory,
12
+ } from "../core/conversation-grounding.js";
9
13
 
10
14
  const FORGOTTEN = "(很久没想起了)";
11
15
 
@@ -46,6 +50,12 @@ function groupStyle(mind, session, now) {
46
50
  )
47
51
  .all(session, now + 60000);
48
52
  const ids = rows.map((row) => row.event_id).filter(Boolean);
53
+ const bots = new Set(
54
+ mind.relationships
55
+ .context({ room: session, now, limit: 100 })
56
+ .known.filter((p) => p.kind === "bot")
57
+ .map((p) => String(p.subjectId)),
58
+ );
49
59
  const away = ids.length
50
60
  ? new Set(
51
61
  mind.db
@@ -57,7 +67,9 @@ function groupStyle(mind, session, now) {
57
67
  )
58
68
  : new Set();
59
69
  const profile = summarizeGroupStyle(
60
- away.size ? rows.filter((row) => !away.has(row.event_id)) : rows,
70
+ rows.filter(
71
+ (row) => !away.has(row.event_id) && !bots.has(String(row.user_id)),
72
+ ),
61
73
  now,
62
74
  );
63
75
  return profile.ready ? profile.summary : "";
@@ -97,11 +109,31 @@ export function innerView(
97
109
  now,
98
110
  });
99
111
  const carries = (thread) => mind.meetings.stays(thread, session, now);
112
+ const availableThreads = mind.self
113
+ .annotated({ before: now, now })
114
+ .filter(
115
+ (t) =>
116
+ !t.faded &&
117
+ carries(t) &&
118
+ !fixedReplyHabit(t.content) &&
119
+ !(
120
+ t.kind === "habit" &&
121
+ t.origin === "memory" &&
122
+ new Set(t.days).size < 3
123
+ ),
124
+ );
125
+ // Her cached self stays the same across cues. Topic-specific recollections
126
+ // belong in inner, so a new question does not swap her personality.
100
127
  const visibleThreads = relevantContext(
101
- mind.self
102
- .annotated({ before: now, now })
103
- .filter((t) => !t.faded && carries(t)),
104
- { cues, limit: 6, pinned: (t) => t.core || t.thread === living?.thread },
128
+ availableThreads.filter((t) => t.core || t.thread === living?.thread),
129
+ {
130
+ limit: 6,
131
+ pinned: (t) => t.core || t.thread === living?.thread,
132
+ },
133
+ );
134
+ const relatedSelf = relevantContext(
135
+ availableThreads.filter((t) => !visibleThreads.includes(t)),
136
+ { cues, limit: 2, requireOverlap: true },
105
137
  );
106
138
  const shownLiving = living && carries(living) ? living : null;
107
139
  const reminded = [
@@ -143,9 +175,14 @@ export function innerView(
143
175
  ? {
144
176
  here: describeFace({
145
177
  ...face,
146
- notes: face.notes.filter((note) =>
147
- spoken(`${note.content} ${note.why}`),
148
- ),
178
+ notes: face.notes
179
+ .map((note) => ({
180
+ ...note,
181
+ content: conversationalMemory(note.content),
182
+ }))
183
+ .filter(
184
+ (note) => note.content && spoken(`${note.content} ${note.why}`),
185
+ ),
149
186
  role: spoken(face.role),
150
187
  tone: spoken(face.tone),
151
188
  aspiration: spoken(face.aspiration),
@@ -211,8 +248,12 @@ export function innerView(
211
248
  const thoughts = relevantContext(
212
249
  mind.thoughts
213
250
  .open({ now, limit: 400, session })
214
- .filter((t) => mind.meetings.sayable(t.content, session)),
215
- { cues, limit: 3 },
251
+ .filter(
252
+ (t) =>
253
+ !fixedReplyHabit(t.content) &&
254
+ mind.meetings.sayable(t.content, session),
255
+ ),
256
+ { cues, limit: 3, requireOverlap: cue.length > 0 },
216
257
  );
217
258
  const heard = feedback(mind, session, now);
218
259
  const continuity = mind.continuity.recall({ session, people, cue, now });
@@ -228,6 +269,7 @@ export function innerView(
228
269
  });
229
270
  const wills = mind.meetings.traces({ session, before: now, limit: 2 });
230
271
  const will = wills
272
+ .filter((row) => !fixedReplyHabit(row.text))
231
273
  .map((row) =>
232
274
  row.living ? row.text : `${text(row.content, 28)}:${row.text}`,
233
275
  )
@@ -243,8 +285,8 @@ export function innerView(
243
285
  affect.cause && mind.meetings.sayable(affect.cause, session)
244
286
  ? text(affect.cause, 40)
245
287
  : "";
246
- const withLines = withWhom.filter((line) =>
247
- mind.meetings.sayable(line, session),
288
+ const withLines = withWhom.filter(
289
+ (line) => !fixedReplyHabit(line) && mind.meetings.sayable(line, session),
248
290
  );
249
291
  const expectingLines = expecting.filter((line) =>
250
292
  mind.meetings.sayable(line, session),
@@ -272,6 +314,14 @@ export function innerView(
272
314
  ),
273
315
  }
274
316
  : {}),
317
+ ...(relatedSelf.length
318
+ ? {
319
+ relatedSelf: relatedSelf.map(
320
+ (t) =>
321
+ `${SELF_KINDS[t.kind]}:${relationshipHistory(text(t.content, 80), t.created, relationships)}`,
322
+ ),
323
+ }
324
+ : {}),
275
325
  ...(expectingLines.length ? { expecting: expectingLines } : {}),
276
326
  ...(withLines.length ? { with: withLines } : {}),
277
327
  ...(will ? { will } : {}),