luckytri 1.0.6 → 1.0.7

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 (155) hide show
  1. package/LICENSE +21 -21
  2. package/docs/en/CHANGELOG.md +25 -16
  3. package/docs/en/chat-style.md +28 -9
  4. package/docs/en/guide.md +5 -5
  5. package/docs/en/her-life.md +2 -2
  6. package/docs/en/webui.md +1 -1
  7. package/docs/zh/CHANGELOG.md +25 -16
  8. package/docs/zh/chat-style.md +30 -9
  9. package/docs/zh/guide.md +5 -5
  10. package/docs/zh/her-life.md +2 -2
  11. package/docs/zh/webui.md +1 -1
  12. package/package.json +1 -1
  13. package/public/app/assets/{ChatsPage-DCYMkJ91.js → ChatsPage-CshdtTzV.js} +2 -2
  14. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  15. package/public/app/assets/{Empty-CLyZkxR5.js → Empty-C7yzOBzy.js} +1 -1
  16. package/public/app/assets/{Empty-DIobYG3x.css → Empty-DmUB2-Bk.css} +1 -1
  17. package/public/app/assets/{Field-BpaRwLI_.js → Field-BjFv4Rfn.js} +1 -1
  18. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  19. package/public/app/assets/{HeartPage-BIOMTiwr.js → HeartPage-eqo6ru16.js} +1 -1
  20. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  21. package/public/app/assets/{LifePage-BCgM4JkR.js → LifePage-CN8GE5NK.js} +1 -1
  22. package/public/app/assets/{MemoryPage-vShvRqMt.js → MemoryPage-CCpMN4Wg.js} +1 -1
  23. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  24. package/public/app/assets/{NaturePage-Cy9ptr4G.js → NaturePage-C0JqdBTR.js} +2 -2
  25. package/public/app/assets/NaturePage-CHkpb5NK.css +1 -0
  26. package/public/app/assets/PeoplePage-BTU0H1pM.css +1 -0
  27. package/public/app/assets/{PeoplePage-CUZekB2E.js → PeoplePage-BXGBbO4u.js} +1 -1
  28. package/public/app/assets/{PluginsPage-1RM4rmJP.js → PluginsPage-Dksp5AV5.js} +1 -1
  29. package/public/app/assets/{SystemPage-BI2CB5Ig.css → SystemPage-BenIyTvk.css} +1 -1
  30. package/public/app/assets/{SystemPage-BeT5TmIV.js → SystemPage-Cd7aa8eE.js} +1 -1
  31. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  32. package/public/app/assets/{TimePage-taTmPMM8.js → TimePage-Bs_zImb4.js} +2 -2
  33. package/public/app/assets/{index-DY5AM0Hz.css → index-BHZcHUMh.css} +1 -1
  34. package/public/app/assets/{index-DdYwC5ZU.js → index-CyRtYxP3.js} +3 -3
  35. package/public/app/assets/{knowledge-DJmfu2KN.js → knowledge-BJOIyuJo.js} +1 -1
  36. package/public/app/assets/{media-BEtnE0Gv.js → media-Bwtx8blz.js} +1 -1
  37. package/public/app/assets/{mind-Dtiw0-QK.js → mind-DtTVrW6Z.js} +1 -1
  38. package/public/app/index.html +5 -6
  39. package/public/guide.en.html +3 -3
  40. package/public/guide.html +3 -3
  41. package/scripts/import-data.js +128 -128
  42. package/scripts/proxy-config.js +167 -167
  43. package/scripts/update-instance.js +201 -201
  44. package/server/channels/adapters.js +27 -27
  45. package/server/channels/onebot/adapter.js +288 -288
  46. package/server/channels/qqbot/adapter.js +253 -253
  47. package/server/channels/qqbot/api.js +199 -199
  48. package/server/channels/qqbot/credentials.js +11 -11
  49. package/server/channels/qqbot/gateway.js +274 -274
  50. package/server/channels/qqbot/index.js +389 -389
  51. package/server/channels/segments.js +40 -40
  52. package/server/channels/session-key.js +153 -153
  53. package/server/channels/state.js +42 -42
  54. package/server/core/api.js +564 -564
  55. package/server/core/context-builder.js +288 -288
  56. package/server/core/context-compactor.js +454 -454
  57. package/server/core/conversation-cues.js +365 -325
  58. package/server/core/conversation-grounding.js +178 -0
  59. package/server/core/conversation-manager.js +87 -87
  60. package/server/core/dialogue-context.js +52 -0
  61. package/server/core/feedback.js +8 -8
  62. package/server/core/group-style.js +72 -72
  63. package/server/core/initiative-context.js +66 -66
  64. package/server/core/input-budget.js +32 -32
  65. package/server/core/local-demo.js +98 -98
  66. package/server/core/message-manager.js +118 -118
  67. package/server/core/message-scheduler.js +78 -78
  68. package/server/core/model-json.js +190 -190
  69. package/server/core/model-presets.js +900 -900
  70. package/server/core/name-call.js +29 -29
  71. package/server/core/network.js +157 -157
  72. package/server/core/person-name.js +22 -22
  73. package/server/core/persona-manager.js +324 -269
  74. package/server/core/reply-delivery.js +472 -404
  75. package/server/core/reply-target-resolver.js +50 -50
  76. package/server/core/response-generator.js +58 -50
  77. package/server/core/response-validator.js +30 -27
  78. package/server/core/retired-prompts.js +51 -51
  79. package/server/core/sessions.js +109 -109
  80. package/server/core/speaker-names.js +92 -92
  81. package/server/core/topic-tracker.js +65 -65
  82. package/server/core/turn.js +42 -8
  83. package/server/http.js +18 -18
  84. package/server/i18n/decisions.js +209 -209
  85. package/server/i18n/index.js +144 -144
  86. package/server/i18n/labels.js +0 -5
  87. package/server/i18n/models.js +111 -111
  88. package/server/knowledge/api.js +342 -342
  89. package/server/knowledge/embedding-profile.js +114 -114
  90. package/server/knowledge/index.js +13 -13
  91. package/server/knowledge/retrieval.js +93 -93
  92. package/server/knowledge/schema.js +158 -158
  93. package/server/knowledge/vector-index.js +90 -90
  94. package/server/mind/affect.js +265 -265
  95. package/server/mind/attention.js +200 -200
  96. package/server/mind/bonds.js +565 -565
  97. package/server/mind/clock.js +83 -83
  98. package/server/mind/context-selection.js +16 -4
  99. package/server/mind/continuity.js +562 -562
  100. package/server/mind/conversation-origin.js +175 -175
  101. package/server/mind/days.js +210 -210
  102. package/server/mind/evidence-read.js +45 -45
  103. package/server/mind/faces.js +373 -373
  104. package/server/mind/guard.js +68 -60
  105. package/server/mind/initiative.js +338 -338
  106. package/server/mind/life/activities.js +21 -21
  107. package/server/mind/life/context.js +330 -330
  108. package/server/mind/life/growth.js +261 -261
  109. package/server/mind/life/presence.js +305 -305
  110. package/server/mind/meetings.js +1069 -1069
  111. package/server/mind/memory-policy.js +48 -48
  112. package/server/mind/memory.js +888 -888
  113. package/server/mind/nature.js +358 -358
  114. package/server/mind/novelty.js +19 -19
  115. package/server/mind/periods.js +118 -118
  116. package/server/mind/reading.js +120 -120
  117. package/server/mind/relationship-context.js +71 -71
  118. package/server/mind/relationships.js +390 -390
  119. package/server/mind/salience.js +201 -201
  120. package/server/mind/schema.js +567 -567
  121. package/server/mind/self.js +496 -484
  122. package/server/mind/thoughts.js +274 -274
  123. package/server/mind/time/agenda.js +242 -242
  124. package/server/mind/time/api.js +200 -200
  125. package/server/mind/time/attention.js +34 -34
  126. package/server/mind/time/delivery-links.js +130 -130
  127. package/server/mind/time/games.js +7 -3
  128. package/server/mind/time/index.js +3 -0
  129. package/server/mind/time/model-material.js +77 -77
  130. package/server/mind/time/presentation.js +35 -51
  131. package/server/mind/time/schedule.js +83 -83
  132. package/server/mind/time/schema.js +84 -84
  133. package/server/mind/time/search.js +383 -383
  134. package/server/mind/time/sharing.js +472 -472
  135. package/server/mind/time/task-links.js +311 -311
  136. package/server/mind/time/works.js +438 -432
  137. package/server/mind/traits.js +191 -191
  138. package/server/mind/view.js +43 -6
  139. package/server/runtime/installation.js +116 -116
  140. package/server/runtime/update-manager.js +140 -140
  141. package/server/storage/data-package.js +298 -298
  142. package/server/storage/data-transfer-api.js +50 -50
  143. package/server/storage/data-transfer-worker.js +18 -18
  144. package/server/storage/data-transfer.js +416 -416
  145. package/server/studio/display-names.js +139 -139
  146. package/server/studio/management.js +102 -102
  147. package/server/studio/updates.js +144 -144
  148. package/server/version.js +10 -10
  149. package/public/app/assets/ChatsPage-CFMVozoG.css +0 -1
  150. package/public/app/assets/HeartPage-C-2jvgzF.css +0 -1
  151. package/public/app/assets/LifePage-CG1OHbRK.css +0 -1
  152. package/public/app/assets/MemoryPage-C_Bu4rfb.css +0 -1
  153. package/public/app/assets/NaturePage-CPZlImT0.css +0 -1
  154. package/public/app/assets/PeoplePage-3RRUITDz.css +0 -1
  155. 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,7 @@ 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 { fixedReplyHabit } from "../core/conversation-grounding.js";
9
10
 
10
11
  const FORGOTTEN = "(很久没想起了)";
11
12
 
@@ -46,6 +47,12 @@ function groupStyle(mind, session, now) {
46
47
  )
47
48
  .all(session, now + 60000);
48
49
  const ids = rows.map((row) => row.event_id).filter(Boolean);
50
+ const bots = new Set(
51
+ mind.relationships
52
+ .context({ room: session, now, limit: 100 })
53
+ .known.filter((p) => p.kind === "bot")
54
+ .map((p) => String(p.subjectId)),
55
+ );
49
56
  const away = ids.length
50
57
  ? new Set(
51
58
  mind.db
@@ -57,7 +64,9 @@ function groupStyle(mind, session, now) {
57
64
  )
58
65
  : new Set();
59
66
  const profile = summarizeGroupStyle(
60
- away.size ? rows.filter((row) => !away.has(row.event_id)) : rows,
67
+ rows.filter(
68
+ (row) => !away.has(row.event_id) && !bots.has(String(row.user_id)),
69
+ ),
61
70
  now,
62
71
  );
63
72
  return profile.ready ? profile.summary : "";
@@ -97,11 +106,31 @@ export function innerView(
97
106
  now,
98
107
  });
99
108
  const carries = (thread) => mind.meetings.stays(thread, session, now);
109
+ const availableThreads = mind.self
110
+ .annotated({ before: now, now })
111
+ .filter(
112
+ (t) =>
113
+ !t.faded &&
114
+ carries(t) &&
115
+ !fixedReplyHabit(t.content) &&
116
+ !(
117
+ t.kind === "habit" &&
118
+ t.origin === "memory" &&
119
+ new Set(t.days).size < 3
120
+ ),
121
+ );
122
+ // Her cached self stays the same across cues. Topic-specific recollections
123
+ // belong in inner, so a new question does not swap her personality.
100
124
  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 },
125
+ availableThreads.filter((t) => t.core || t.thread === living?.thread),
126
+ {
127
+ limit: 6,
128
+ pinned: (t) => t.core || t.thread === living?.thread,
129
+ },
130
+ );
131
+ const relatedSelf = relevantContext(
132
+ availableThreads.filter((t) => !visibleThreads.includes(t)),
133
+ { cues, limit: 2, requireOverlap: true },
105
134
  );
106
135
  const shownLiving = living && carries(living) ? living : null;
107
136
  const reminded = [
@@ -212,7 +241,7 @@ export function innerView(
212
241
  mind.thoughts
213
242
  .open({ now, limit: 400, session })
214
243
  .filter((t) => mind.meetings.sayable(t.content, session)),
215
- { cues, limit: 3 },
244
+ { cues, limit: 3, requireOverlap: cue.length > 0 },
216
245
  );
217
246
  const heard = feedback(mind, session, now);
218
247
  const continuity = mind.continuity.recall({ session, people, cue, now });
@@ -272,6 +301,14 @@ export function innerView(
272
301
  ),
273
302
  }
274
303
  : {}),
304
+ ...(relatedSelf.length
305
+ ? {
306
+ relatedSelf: relatedSelf.map(
307
+ (t) =>
308
+ `${SELF_KINDS[t.kind]}:${relationshipHistory(text(t.content, 80), t.created, relationships)}`,
309
+ ),
310
+ }
311
+ : {}),
275
312
  ...(expectingLines.length ? { expecting: expectingLines } : {}),
276
313
  ...(withLines.length ? { with: withLines } : {}),
277
314
  ...(will ? { will } : {}),