luckytri 1.0.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.
Files changed (235) hide show
  1. package/.github/SECURITY.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +250 -0
  4. package/README.md +250 -0
  5. package/bin/luckytri.js +107 -0
  6. package/docs/brand/banner.png +0 -0
  7. package/docs/brand/palette.png +0 -0
  8. package/docs/brand/portrait.png +0 -0
  9. package/docs/en/CHANGELOG.md +113 -0
  10. package/docs/en/architecture.md +152 -0
  11. package/docs/en/chat-style.md +62 -0
  12. package/docs/en/connect.md +63 -0
  13. package/docs/en/guide.md +216 -0
  14. package/docs/en/her-life.md +189 -0
  15. package/docs/en/npm-release.md +25 -0
  16. package/docs/en/plugins.md +82 -0
  17. package/docs/en/recovery.md +28 -0
  18. package/docs/en/time.md +71 -0
  19. package/docs/en/webui.md +73 -0
  20. package/docs/zh/CHANGELOG.md +113 -0
  21. package/docs/zh/architecture.md +152 -0
  22. package/docs/zh/chat-style.md +62 -0
  23. package/docs/zh/connect.md +63 -0
  24. package/docs/zh/guide.md +216 -0
  25. package/docs/zh/her-life.md +189 -0
  26. package/docs/zh/npm-release.md +33 -0
  27. package/docs/zh/plugins.md +82 -0
  28. package/docs/zh/recovery.md +28 -0
  29. package/docs/zh/time.md +71 -0
  30. package/docs/zh/webui.md +73 -0
  31. package/package.json +108 -0
  32. package/plugins/sdk/index.d.ts +155 -0
  33. package/plugins/sdk/testing.js +58 -0
  34. package/plugins/weather/index.js +82 -0
  35. package/plugins/weather/luckytri-plugin.json +29 -0
  36. package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
  37. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  38. package/public/app/assets/Empty-CpP5kQHH.js +2 -0
  39. package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
  40. package/public/app/assets/Field-D4gY-INU.js +1 -0
  41. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  42. package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
  43. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  44. package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
  45. package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
  46. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  47. package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
  48. package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
  49. package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
  50. package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
  51. package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
  52. package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
  53. package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
  54. package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
  55. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  56. package/public/app/assets/TimePage-Cw44inM4.js +3 -0
  57. package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
  58. package/public/app/assets/index-BheRW-1t.js +4 -0
  59. package/public/app/assets/index-YDz1ree1.css +1 -0
  60. package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
  61. package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
  62. package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
  63. package/public/app/assets/media-BzPDBm7e.js +1 -0
  64. package/public/app/index.html +27 -0
  65. package/public/guide.en.html +1053 -0
  66. package/public/guide.html +893 -0
  67. package/public/plugin-kit/v1/bridge.js +30 -0
  68. package/public/plugin-kit/v1/kit.css +14 -0
  69. package/public/tutorial.en.md +214 -0
  70. package/public/tutorial.md +214 -0
  71. package/scripts/backup.js +311 -0
  72. package/scripts/browser.js +41 -0
  73. package/scripts/launch.js +94 -0
  74. package/scripts/plugin.js +150 -0
  75. package/scripts/proxy-config.js +167 -0
  76. package/scripts/recover.js +26 -0
  77. package/scripts/runtime.js +25 -0
  78. package/scripts/service.js +47 -0
  79. package/scripts/setup.js +50 -0
  80. package/scripts/start-server.js +41 -0
  81. package/scripts/stop.js +36 -0
  82. package/server/app.js +96 -0
  83. package/server/channels/adapters.js +27 -0
  84. package/server/channels/contract.js +57 -0
  85. package/server/channels/hub.js +165 -0
  86. package/server/channels/index.js +25 -0
  87. package/server/channels/onebot/adapter.js +288 -0
  88. package/server/channels/onebot/index.js +336 -0
  89. package/server/channels/onebot/token.js +3 -0
  90. package/server/channels/qqbot/adapter.js +253 -0
  91. package/server/channels/qqbot/api.js +199 -0
  92. package/server/channels/qqbot/credentials.js +11 -0
  93. package/server/channels/qqbot/gateway.js +274 -0
  94. package/server/channels/qqbot/index.js +389 -0
  95. package/server/channels/segments.js +40 -0
  96. package/server/channels/session-key.js +153 -0
  97. package/server/channels/state.js +42 -0
  98. package/server/core/api.js +564 -0
  99. package/server/core/capabilities.js +208 -0
  100. package/server/core/context-builder.js +288 -0
  101. package/server/core/context-compactor.js +454 -0
  102. package/server/core/conversation-cues.js +325 -0
  103. package/server/core/conversation-manager.js +87 -0
  104. package/server/core/events.js +60 -0
  105. package/server/core/feedback.js +8 -0
  106. package/server/core/group-style.js +72 -0
  107. package/server/core/image-preprocessor.js +208 -0
  108. package/server/core/initiative-context.js +66 -0
  109. package/server/core/input-budget.js +32 -0
  110. package/server/core/local-demo.js +98 -0
  111. package/server/core/message-manager.js +118 -0
  112. package/server/core/message-scheduler.js +78 -0
  113. package/server/core/model-json.js +190 -0
  114. package/server/core/model-manager.js +1213 -0
  115. package/server/core/model-presets.js +900 -0
  116. package/server/core/name-call.js +29 -0
  117. package/server/core/net-guard.js +49 -0
  118. package/server/core/network.js +157 -0
  119. package/server/core/orchestrator.js +569 -0
  120. package/server/core/perceivers.js +91 -0
  121. package/server/core/person-name.js +22 -0
  122. package/server/core/persona-manager.js +243 -0
  123. package/server/core/reply-delivery.js +393 -0
  124. package/server/core/reply-target-resolver.js +50 -0
  125. package/server/core/repository.js +293 -0
  126. package/server/core/response-generator.js +50 -0
  127. package/server/core/response-validator.js +508 -0
  128. package/server/core/retired-prompts.js +51 -0
  129. package/server/core/sessions.js +109 -0
  130. package/server/core/speaker-names.js +92 -0
  131. package/server/core/topic-tracker.js +65 -0
  132. package/server/core/turn-processor.js +630 -0
  133. package/server/core/turn.js +236 -0
  134. package/server/core/vision-manager.js +633 -0
  135. package/server/http.js +18 -0
  136. package/server/i18n/decisions.js +206 -0
  137. package/server/i18n/errors.js +497 -0
  138. package/server/i18n/index.js +144 -0
  139. package/server/i18n/labels.js +653 -0
  140. package/server/i18n/models.js +111 -0
  141. package/server/index.js +102 -0
  142. package/server/knowledge/api.js +342 -0
  143. package/server/knowledge/embedding-profile.js +114 -0
  144. package/server/knowledge/index.js +13 -0
  145. package/server/knowledge/manager.js +386 -0
  146. package/server/knowledge/retrieval.js +93 -0
  147. package/server/knowledge/schema.js +154 -0
  148. package/server/knowledge/vector-index.js +90 -0
  149. package/server/mind/affect.js +265 -0
  150. package/server/mind/anticipations.js +507 -0
  151. package/server/mind/api.js +703 -0
  152. package/server/mind/attention.js +200 -0
  153. package/server/mind/bonds.js +558 -0
  154. package/server/mind/budget.js +152 -0
  155. package/server/mind/clock.js +83 -0
  156. package/server/mind/context-selection.js +61 -0
  157. package/server/mind/continuity.js +562 -0
  158. package/server/mind/conversation-origin.js +175 -0
  159. package/server/mind/day-planner.js +324 -0
  160. package/server/mind/days.js +210 -0
  161. package/server/mind/evidence-read.js +45 -0
  162. package/server/mind/evidence.js +148 -0
  163. package/server/mind/faces.js +138 -0
  164. package/server/mind/guard.js +60 -0
  165. package/server/mind/index.js +445 -0
  166. package/server/mind/initiative.js +333 -0
  167. package/server/mind/life/activities.js +21 -0
  168. package/server/mind/life/context.js +309 -0
  169. package/server/mind/life/diary.js +786 -0
  170. package/server/mind/life/growth.js +252 -0
  171. package/server/mind/life/index.js +1049 -0
  172. package/server/mind/life/presence.js +305 -0
  173. package/server/mind/meetings.js +1069 -0
  174. package/server/mind/memory-policy.js +48 -0
  175. package/server/mind/memory.js +888 -0
  176. package/server/mind/nature.js +262 -0
  177. package/server/mind/novelty.js +19 -0
  178. package/server/mind/observations.js +84 -0
  179. package/server/mind/own-day.js +324 -0
  180. package/server/mind/own-voice.js +318 -0
  181. package/server/mind/periods.js +118 -0
  182. package/server/mind/reading.js +120 -0
  183. package/server/mind/salience.js +201 -0
  184. package/server/mind/schema.js +562 -0
  185. package/server/mind/self.js +484 -0
  186. package/server/mind/senses.js +53 -0
  187. package/server/mind/thoughts.js +274 -0
  188. package/server/mind/time/activity-clock.js +196 -0
  189. package/server/mind/time/agenda.js +242 -0
  190. package/server/mind/time/api.js +200 -0
  191. package/server/mind/time/attention.js +34 -0
  192. package/server/mind/time/availability.js +69 -0
  193. package/server/mind/time/commitment-review.js +198 -0
  194. package/server/mind/time/delivery-links.js +130 -0
  195. package/server/mind/time/executor.js +512 -0
  196. package/server/mind/time/experiences.js +128 -0
  197. package/server/mind/time/games.js +562 -0
  198. package/server/mind/time/index.js +739 -0
  199. package/server/mind/time/intent.js +166 -0
  200. package/server/mind/time/kinds.js +171 -0
  201. package/server/mind/time/model-material.js +77 -0
  202. package/server/mind/time/plugin-activity.js +207 -0
  203. package/server/mind/time/presentation.js +51 -0
  204. package/server/mind/time/schedule.js +83 -0
  205. package/server/mind/time/schema.js +84 -0
  206. package/server/mind/time/search.js +383 -0
  207. package/server/mind/time/sharing.js +472 -0
  208. package/server/mind/time/task-links.js +311 -0
  209. package/server/mind/time/tasks.js +587 -0
  210. package/server/mind/time/works.js +432 -0
  211. package/server/mind/traits.js +191 -0
  212. package/server/mind/util.js +110 -0
  213. package/server/mind/view.js +273 -0
  214. package/server/paths.js +102 -0
  215. package/server/plugins/api.js +204 -0
  216. package/server/plugins/host.js +967 -0
  217. package/server/plugins/install.js +295 -0
  218. package/server/plugins/manifest.js +201 -0
  219. package/server/plugins/permissions.js +42 -0
  220. package/server/plugins/runner/guard.mjs +36 -0
  221. package/server/plugins/runner/worker.mjs +250 -0
  222. package/server/plugins/schema.js +91 -0
  223. package/server/plugins/zip.js +112 -0
  224. package/server/startup-lock.js +118 -0
  225. package/server/storage/archive.js +241 -0
  226. package/server/storage/backup-cleanup-worker.js +8 -0
  227. package/server/storage/backup-cleanup.js +414 -0
  228. package/server/storage/backup-lock.js +76 -0
  229. package/server/storage/backup-scheduler.js +131 -0
  230. package/server/storage/store.js +199 -0
  231. package/server/studio/display-names.js +138 -0
  232. package/server/studio/management.js +102 -0
  233. package/server/studio/readiness.js +190 -0
  234. package/server/studio/settings.js +236 -0
  235. package/server/version.js +10 -0
@@ -0,0 +1,786 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { localClock } from "../../core/conversation-cues.js";
3
+ import { replyPrompt, prompts } from "../../core/persona-manager.js";
4
+ import { estimateTokens, withFallback } from "../../core/model-manager.js";
5
+ import { diffSnapshots } from "../index.js";
6
+ import { lifeDayStart } from "../nature.js";
7
+ import { SELF_KINDS } from "../self.js";
8
+ import { aboutLife, ownLife } from "../salience.js";
9
+ import { DAY, HOUR, hasCredential, parse, text } from "../util.js";
10
+ const MINUTE = 60000;
11
+ const LIVE = "COALESCE(json_extract(payload,'$.simulated'),0)=0";
12
+ const SOLITUDE_INPUT_CAP = 24000;
13
+ const NIGHT_PENDING = 6;
14
+
15
+ // Owns this part of the lifecycle; the facade keeps the shared runtime state.
16
+ export class LifeDiary {
17
+ constructor(owner) {
18
+ this.owner = owner;
19
+ }
20
+ diaryDue(now = this.owner.now()) {
21
+ const s = this.owner.settings();
22
+ if (!s.diary) return null;
23
+ try {
24
+ this.owner.profile();
25
+ } catch {
26
+ return null;
27
+ }
28
+ if (!this.owner.mind.budget.allows("inner", now)) return null;
29
+ const phase = this.owner.phase(now).key;
30
+ const nature = this.owner.mind.nature.current(now);
31
+ const bedtime = nature.rhythm?.enabled
32
+ ? ["sleepy", "asleep"].includes(phase)
33
+ : localClock(now, this.owner.mind.timeZone()).hour >= 23;
34
+ const day = this.owner.lifeDay(now);
35
+ // A diary that failed to come out waits a while, and a day is given up
36
+ // after a few tries rather than retried every minute.
37
+ const has = (d) => {
38
+ if (
39
+ this.owner.db
40
+ .prepare("SELECT 1 FROM mind_diary WHERE day=? LIMIT 1")
41
+ .get(d)
42
+ )
43
+ return true;
44
+ const tries = this.owner.db
45
+ .prepare(
46
+ "SELECT COUNT(*) n, MAX(started) last FROM mind_runs WHERE kind='daily' AND json_extract(summary,'$.day')=?",
47
+ )
48
+ .get(d);
49
+ return tries.n >= 3 || (tries.last && now - tries.last < 3 * HOUR);
50
+ };
51
+ const start = this.owner.dayStart(now);
52
+ if (
53
+ bedtime &&
54
+ !has(day) &&
55
+ this.owner.mind.days.participated(start, now, { inclusive: true })
56
+ )
57
+ return { day, start, end: now };
58
+ const yesterday = this.owner.lifeDay(start - MINUTE);
59
+ const before = this.owner.dayStart(start - MINUTE);
60
+ if (
61
+ !bedtime &&
62
+ !has(yesterday) &&
63
+ this.owner.mind.days.participated(before, start)
64
+ )
65
+ return { day: yesterday, start: before, end: start };
66
+ return null;
67
+ }
68
+ dayStart(now) {
69
+ return lifeDayStart(
70
+ this.owner.mind.nature.current(now),
71
+ now,
72
+ this.owner.mind.timeZone(),
73
+ );
74
+ }
75
+ diaryLine(entry, now = this.owner.now()) {
76
+ const closed =
77
+ this.owner.mind.meetings.privateBeyond(entry.day, "", now) ||
78
+ !this.owner.mind.meetings.sayable(entry.content, "");
79
+ const compare =
80
+ !closed &&
81
+ entry.compare &&
82
+ this.owner.mind.meetings.sayable(entry.compare, "")
83
+ ? text(entry.compare, 120)
84
+ : "";
85
+ return {
86
+ ref: `d:${entry.day}`,
87
+ day: entry.day,
88
+ ...(entry.mood ? { mood: entry.mood } : {}),
89
+ content: closed
90
+ ? "这一天有留在私下的事,原文不带到回顾里。"
91
+ : text(entry.content, 400),
92
+ ...(compare ? { compare } : {}),
93
+ ...(closed ? { private: true } : {}),
94
+ };
95
+ }
96
+ openWords(value, limit) {
97
+ let out = String(value || "");
98
+ for (const saying of this.owner.mind.meetings.privateSayings(""))
99
+ if (saying.content) out = out.split(saying.content).join("");
100
+ out = out
101
+ .replace(/[。!?]{2,}/g, (mark) => mark[0])
102
+ .replace(/\n{3,}/g, "\n\n")
103
+ .trim();
104
+ if (!out || !this.owner.mind.meetings.sayable(out, "")) return "";
105
+ return text(out, limit);
106
+ }
107
+ openThreads(rows, now = this.owner.now()) {
108
+ const known = this.owner.mind.self.latest(now);
109
+ return (rows || []).filter((item) => {
110
+ const row = known.find((thread) => thread.thread === item.thread);
111
+ return !!row && this.owner.mind.meetings.stays(row, "");
112
+ });
113
+ }
114
+ chapterView(now, size = 120) {
115
+ const chapter = this.owner.mind.periods.current(now);
116
+ if (!chapter) return null;
117
+ const gist = this.#presentLifeWriting(chapter.content, now, size);
118
+ return {
119
+ number: chapter.chapter,
120
+ title: chapter.title,
121
+ ...(gist ? { gist } : {}),
122
+ };
123
+ }
124
+ #lifeMoved(now) {
125
+ const living = this.owner.mind.self.living({ now, before: now });
126
+ const chapter = this.owner.mind.periods.current(now);
127
+ return !!(
128
+ living &&
129
+ ownLife(living.content) &&
130
+ chapter &&
131
+ !aboutLife(living.content, chapter.content)
132
+ );
133
+ }
134
+ #presentLifeWriting(content, now, size) {
135
+ let body = String(content || "");
136
+ const living = this.owner.mind.self.living({ now, before: now });
137
+ if (
138
+ living &&
139
+ ownLife(living.content) &&
140
+ body &&
141
+ !aboutLife(living.content, body)
142
+ ) {
143
+ const duties = this.owner.mind.self
144
+ .latest(now)
145
+ .filter(
146
+ (row) =>
147
+ row.kind === "intention" &&
148
+ row.status !== "closed" &&
149
+ !ownLife(row.content),
150
+ );
151
+ body = body
152
+ .split(/(?<=[。\n])/)
153
+ .filter((sentence) => {
154
+ if (
155
+ /为自己而活的那件事/.test(sentence) &&
156
+ !aboutLife(living.content, sentence)
157
+ )
158
+ return false;
159
+ return !duties.some((row) => aboutLife(row.content, sentence));
160
+ })
161
+ .join("");
162
+ }
163
+ return this.owner.openWords(body, size);
164
+ }
165
+ async review({ day, start, end }) {
166
+ this.owner.busy = true;
167
+ const now = this.owner.now();
168
+ const id = this.owner.run("daily", `写 ${day} 的日记`);
169
+ const trace = this.owner.repo.trace("__mind__", "daily");
170
+ let status = "empty";
171
+ let reason = "今天没有写下什么";
172
+ let summary = { day };
173
+ try {
174
+ const nature = this.owner.mind.nature.current(now);
175
+ const since =
176
+ this.owner.db
177
+ .prepare(
178
+ `SELECT COALESCE(MIN(seq),1)-1 n FROM core_events WHERE time>? AND ${LIVE}`,
179
+ )
180
+ .get(start).n || 0;
181
+ const experiences = this.owner.experiences(since, end, {
182
+ limit: 6,
183
+ rows: 14,
184
+ });
185
+ const thoughts = this.owner.db
186
+ .prepare(
187
+ "SELECT * FROM mind_thoughts WHERE created>? AND created<=? AND hidden=0 ORDER BY created LIMIT 10",
188
+ )
189
+ .all(start, end);
190
+ const choices = this.owner.db
191
+ .prepare(
192
+ "SELECT choice,COUNT(*) n FROM mind_choices WHERE created>? AND created<=? GROUP BY choice",
193
+ )
194
+ .all(start, end);
195
+ const moods = this.owner.mind.affect
196
+ .history({ before: end, limit: 40 })
197
+ .filter((a) => a.created > start)
198
+ .slice(0, 12)
199
+ .map((a) => ({
200
+ at: localClock(a.created, this.owner.mind.timeZone()).local.slice(11),
201
+ feeling: a.feeling,
202
+ cause: text(a.cause, 50),
203
+ }));
204
+ const previousDay = this.owner.db
205
+ .prepare(
206
+ "SELECT day FROM mind_snapshots WHERE day<? ORDER BY day DESC LIMIT 1",
207
+ )
208
+ .get(day)?.day;
209
+ const yesterday = previousDay
210
+ ? this.owner.mind.snapshotOf(previousDay)
211
+ : null;
212
+ const yesterdayLife = yesterday
213
+ ? this.owner.mind.livedThen(yesterday)
214
+ : null;
215
+ const lastDiary = this.owner.db
216
+ .prepare(
217
+ "SELECT day,content,compare FROM mind_diary WHERE day<? ORDER BY day DESC, created DESC LIMIT 1",
218
+ )
219
+ .get(day);
220
+ const earlierDiary =
221
+ lastDiary &&
222
+ !this.owner.mind.meetings.privateBeyond(lastDiary.day, "", end)
223
+ ? text(lastDiary.content, 240)
224
+ : "";
225
+ // Only the short account of her life and the chapter she is in, so
226
+ // the diary does not grow with her age.
227
+ const story = this.owner.mind.periods.story(now);
228
+ const storyWords = this.#presentLifeWriting(story?.content, now, 600);
229
+ const chapter = this.owner.chapterView(now);
230
+ const anniversaries = this.owner.mind.days.anniversaries(end);
231
+ const expected = this.owner.mind.anticipations.today({ start, end });
232
+ const open = this.owner.mind.anticipations.due({
233
+ now: end,
234
+ limit: 5,
235
+ shareable: true,
236
+ });
237
+ const ahead = Object.fromEntries(
238
+ Object.entries({ ...expected, open }).filter(([, v]) => v.length),
239
+ );
240
+ const input = {
241
+ date: day,
242
+ dayOfLife: this.owner.mind.days.dayOfLife(end),
243
+ today: {
244
+ actions: this.owner.mind.time.lived({ since: start, before: end }),
245
+ moods,
246
+ thoughts: thoughts.map((t) => ({
247
+ id: t.id,
248
+ content: text(t.content, 200),
249
+ })),
250
+ choices: Object.fromEntries(choices.map((c) => [c.choice, c.n])),
251
+ feedback: this.owner.feedbackSince(start),
252
+ ...(Object.keys(ahead).length ? { ahead } : {}),
253
+ experiences,
254
+ meetings: this.owner.mind.meetings.held({
255
+ since: start,
256
+ before: end,
257
+ limit: 6,
258
+ }),
259
+ world: this.owner.mind.observations.recent({
260
+ since: start,
261
+ before: end,
262
+ limit: 6,
263
+ }),
264
+ },
265
+ yesterday: yesterday
266
+ ? {
267
+ day: yesterday.day,
268
+ mood: yesterday.affect?.mood,
269
+ ...(yesterdayLife
270
+ ? { livingFor: text(yesterdayLife.content, 80) }
271
+ : {}),
272
+ self: (yesterday.self || [])
273
+ .filter((t) => {
274
+ const row = this.owner.mind.self
275
+ .latest(yesterday.created || end)
276
+ .find((item) => item.thread === t.thread);
277
+ return row && this.owner.mind.meetings.stays(row, "");
278
+ })
279
+ .slice(0, 12)
280
+ .map(
281
+ (t) =>
282
+ `${SELF_KINDS[t.kind] || t.kind}:${t.content}(${t.strength})`,
283
+ ),
284
+ ...(earlierDiary ? { diary: earlierDiary } : {}),
285
+ }
286
+ : null,
287
+ self: this.owner.selfView(now, { open: true }),
288
+ faces: this.owner.faceView(experiences, now),
289
+ livedTraits: this.owner.mind.traits.current(nature, now),
290
+ livedPersona:
291
+ this.owner.mind.traits.persona(nature, now)?.content || "",
292
+ ...(this.owner.livingForView(end, { since: start, open: true })
293
+ ? {
294
+ livingFor: this.owner.livingForView(end, {
295
+ since: start,
296
+ open: true,
297
+ }),
298
+ }
299
+ : {}),
300
+ people: this.owner.peopleIn(experiences, now, { open: true }),
301
+ ...(storyWords ? { story: storyWords } : {}),
302
+ ...(chapter
303
+ ? {
304
+ chapter: {
305
+ ...chapter,
306
+ gist: this.owner.openWords(chapter.gist, 120) || undefined,
307
+ },
308
+ }
309
+ : {}),
310
+ ...(anniversaries.length ? { anniversaries } : {}),
311
+ };
312
+ if (!input.today.meetings.length) delete input.today.meetings;
313
+ if (!input.today.world.length) delete input.today.world;
314
+ while (
315
+ estimateTokens(input) > SOLITUDE_INPUT_CAP &&
316
+ input.today.experiences.some((e) => e.messages.length > 3)
317
+ )
318
+ for (const e of input.today.experiences)
319
+ if (e.messages.length > 3) e.messages.shift();
320
+ while (
321
+ estimateTokens(input) > SOLITUDE_INPUT_CAP &&
322
+ input.today.meetings?.length > 2
323
+ )
324
+ input.today.meetings.shift();
325
+ const result = await this.owner.chat.models.call(
326
+ this.owner.profile(),
327
+ "daily",
328
+ replyPrompt(nature, prompts(this.owner.repo), "daily"),
329
+ input,
330
+ trace,
331
+ );
332
+ const diary = text(result?.diary, 1200);
333
+ if (!diary || hasCredential(diary)) throw SyntaxError("日记格式无效");
334
+ const valid = new Set([
335
+ ...input.today.actions.map((a) => a.ref),
336
+ ...experiences.flatMap((e) => e.messages.map((m) => `m:${m.seq}`)),
337
+ ...thoughts.map((t) => `t:${t.id}`),
338
+ ...input.today.feedback.map((f) => f.ref),
339
+ ...open.map((a) => a.ref),
340
+ ...(input.livingFor ? [`s:${input.livingFor.thread}`] : []),
341
+ ...(input.today.meetings || []).map((m) => m.ref),
342
+ ...(input.today.world || []).map((item) => item.ref),
343
+ ]);
344
+ this.owner.db
345
+ .prepare(
346
+ "INSERT INTO mind_diary(id,day,created,content,mood,compare,sources,run_id) VALUES (?,?,?,?,?,?,?,?)",
347
+ )
348
+ .run(
349
+ randomUUID(),
350
+ day,
351
+ now,
352
+ diary,
353
+ text(result.mood, 16),
354
+ text(result.compare, 300),
355
+ JSON.stringify([...valid].slice(0, 24)),
356
+ id,
357
+ );
358
+ const applied = this.owner.grow(result, valid, "daily", now);
359
+ applied.letGo = this.owner.letGo(
360
+ result?.letGo,
361
+ new Set(thoughts.map((t) => t.id)),
362
+ now,
363
+ );
364
+ Object.assign(
365
+ applied,
366
+ this.owner.anticipate(result, {
367
+ valid,
368
+ shown: new Set(open.map((a) => a.ref.slice(2))),
369
+ origin: "daily",
370
+ now,
371
+ }),
372
+ );
373
+ if (result.mood)
374
+ this.owner.mind.affect.feel({
375
+ feeling: result.mood,
376
+ intensity: 0.2,
377
+ valence: 0,
378
+ cause: "写完了今天的日记",
379
+ origin: "daily",
380
+ time: now,
381
+ });
382
+ this.owner.mind.snapshot(now, day);
383
+ summary = { day, ...applied };
384
+ status = "written";
385
+ reason = `写下了 ${day} 的日记`;
386
+ } catch (error) {
387
+ status = "error";
388
+ reason =
389
+ error instanceof SyntaxError
390
+ ? "日记格式无效,未保存"
391
+ : String(error.message).slice(0, 300);
392
+ trace.error = error.message;
393
+ } finally {
394
+ trace.reason = reason;
395
+ this.owner.chat.finishQuietly(
396
+ trace,
397
+ status === "error" ? "error" : "complete",
398
+ );
399
+ this.owner.end(id, status, reason, trace, summary);
400
+ this.owner.busy = false;
401
+ }
402
+ return { status, reason, runId: id };
403
+ }
404
+ chapters() {
405
+ return this.owner.mind.periods.chapters();
406
+ }
407
+ chapterVersions(chapter) {
408
+ return this.owner.mind.periods.chapterVersions(chapter);
409
+ }
410
+ isNight(now = this.owner.now()) {
411
+ if (this.owner.mind.nature.current(now).rhythm?.enabled)
412
+ return this.owner.phase(now).key === "asleep";
413
+ const hour = localClock(now, this.owner.mind.timeZone()).hour;
414
+ return hour >= 3 && hour < 6;
415
+ }
416
+ nightDue(now = this.owner.now()) {
417
+ if (!this.owner.settings().night || !this.owner.isNight(now)) return null;
418
+ try {
419
+ this.owner.profile();
420
+ } catch {
421
+ return null;
422
+ }
423
+ const day = this.owner.lifeDay(now);
424
+ if (
425
+ this.owner.mind.budget.allows("upkeep", now) &&
426
+ this.owner.repo.store.settings().memoryEnabled !== false
427
+ ) {
428
+ const done = this.owner.db.prepare(
429
+ "SELECT 1 FROM mind_runs WHERE kind='night' AND json_extract(summary,'$.day')=? AND json_extract(summary,'$.session')=? LIMIT 1",
430
+ );
431
+ for (const { id } of this.owner.living())
432
+ if (
433
+ !this.owner.mind.memory.busy.has(id) &&
434
+ this.owner.mind.memory.pending(id) >= NIGHT_PENDING &&
435
+ !done.get(day, id)
436
+ )
437
+ return { kind: "memory", session: id, day };
438
+ }
439
+ if (this.owner.reviewDue(now)) return { kind: "review" };
440
+ return null;
441
+ }
442
+ async rememberAtNight(session, day, now = this.owner.now()) {
443
+ this.owner.busy = true;
444
+ const name =
445
+ this.owner.living().find((s) => s.id === session)?.name || session;
446
+ const id = this.owner.run("night", `夜里整理「${name}」里的事`);
447
+ const trace = this.owner.repo.trace(session, "memory");
448
+ let status = "empty";
449
+ let reason = "没有需要整理的";
450
+ try {
451
+ const profile = this.owner.profile();
452
+ const models = withFallback(
453
+ this.owner.chat.models,
454
+ this.owner.chat.fallbackFor(null, profile, trace),
455
+ );
456
+ const before = this.owner.mind.memory.pending(session);
457
+ await this.owner.mind.memory.consolidate(
458
+ session,
459
+ profile,
460
+ prompts(this.owner.repo).memory,
461
+ trace,
462
+ { force: true, models, now },
463
+ );
464
+ const left = this.owner.mind.memory.pending(session);
465
+ if (left < before) {
466
+ status = "written";
467
+ reason = `夜里整理了「${name}」的 ${before - left} 条消息`;
468
+ }
469
+ } catch (error) {
470
+ status = "error";
471
+ reason = String(error.message).slice(0, 300);
472
+ trace.error = error.message;
473
+ } finally {
474
+ trace.reason = reason;
475
+ this.owner.chat.finishQuietly(
476
+ trace,
477
+ status === "error" ? "error" : "complete",
478
+ );
479
+ this.owner.end(id, status, reason, trace, { day, session });
480
+ this.owner.busy = false;
481
+ }
482
+ return { status, reason, runId: id };
483
+ }
484
+ reviewDue(now = this.owner.now()) {
485
+ const s = this.owner.settings();
486
+ if (!s.diary || !this.owner.mind.budget.allows("inner", now)) return false;
487
+ const last = this.owner.mind.periods.lastReview(now);
488
+ const written = this.owner.db
489
+ .prepare(
490
+ "SELECT COUNT(DISTINCT day) n FROM mind_diary WHERE created>? AND created<=?",
491
+ )
492
+ .get(last?.created ?? 0, now).n;
493
+ if (written < 2) return false;
494
+ const wait = this.#lifeMoved(now) ? DAY : s.chapterDays * DAY - 6 * HOUR;
495
+ if (last && now - last.created < wait) return false;
496
+ const tries = this.owner.db
497
+ .prepare(
498
+ "SELECT COUNT(*) n, MAX(started) last FROM mind_runs WHERE kind='weekly' AND status='error' AND started>=?",
499
+ )
500
+ .get(this.owner.dayStart(now));
501
+ return !(tries.n >= 3 || (tries.last && now - tries.last < 3 * HOUR));
502
+ }
503
+ async reviewPeriod(now = this.owner.now()) {
504
+ this.owner.busy = true;
505
+ const id = this.owner.run("weekly", "回顾这一段日子");
506
+ const trace = this.owner.repo.trace("__mind__", "weekly");
507
+ let status = "empty";
508
+ let reason = "这次没有写下回顾";
509
+ let summary = null;
510
+ try {
511
+ const nature = this.owner.mind.nature.current(now);
512
+ const periods = this.owner.mind.periods;
513
+ const last = periods.lastReview(now);
514
+ const start =
515
+ last?.period_end ??
516
+ this.owner.mind.days.born() ??
517
+ now - this.owner.settings().chapterDays * DAY;
518
+ const diaries = [
519
+ ...new Map(
520
+ this.owner.db
521
+ .prepare(
522
+ "SELECT * FROM mind_diary WHERE created>? AND created<=? ORDER BY day, created",
523
+ )
524
+ .all(last?.created ?? 0, now)
525
+ .map((d) => [d.day, d]),
526
+ ).values(),
527
+ ].slice(-10);
528
+ const first = diaries[0]?.day;
529
+ const earlier = first
530
+ ? this.owner.db
531
+ .prepare(
532
+ "SELECT day FROM mind_snapshots WHERE day<? ORDER BY day DESC LIMIT 1",
533
+ )
534
+ .get(first)?.day
535
+ : null;
536
+ const latest = this.owner.db
537
+ .prepare(
538
+ "SELECT day FROM mind_snapshots WHERE created<=? ORDER BY day DESC LIMIT 1",
539
+ )
540
+ .get(now)?.day;
541
+ const earlierSnap = earlier ? this.owner.mind.snapshotOf(earlier) : null;
542
+ const latestSnap = latest ? this.owner.mind.snapshotOf(latest) : null;
543
+ const asLived = (snap) =>
544
+ snap ? { ...snap, livingFor: this.owner.mind.livedThen(snap) } : null;
545
+ const change =
546
+ earlierSnap && latestSnap
547
+ ? diffSnapshots(asLived(earlierSnap), asLived(latestSnap))
548
+ : null;
549
+ const chapter = periods.current(now);
550
+ const previous = chapter
551
+ ? periods.chapters(now).find((c) => c.chapter === chapter.chapter - 1)
552
+ : null;
553
+ const story = periods.story(now);
554
+ const { kept, missed } = this.owner.mind.anticipations.today({
555
+ start,
556
+ end: now,
557
+ shareable: true,
558
+ });
559
+ const anniversaries = this.owner.mind.days.anniversaries(now);
560
+ const line = (t) =>
561
+ `${SELF_KINDS[t.kind] || t.kind}:${text(t.content, 60)}`;
562
+ const lastReview = last
563
+ ? this.#presentLifeWriting(last.content, now, 300)
564
+ : "";
565
+ const storyWords = story
566
+ ? this.#presentLifeWriting(story.content, now, 600)
567
+ : "";
568
+ const previousWords = previous
569
+ ? this.#presentLifeWriting(previous.content, now, 160)
570
+ : "";
571
+ const input = {
572
+ dayOfLife: this.owner.mind.days.dayOfLife(now),
573
+ period: { from: first, to: diaries.at(-1)?.day },
574
+ diaries: diaries.map((d) => this.owner.diaryLine(d, now)),
575
+ ...(last
576
+ ? {
577
+ lastReview: {
578
+ ...(lastReview ? { content: lastReview } : {}),
579
+ ...(last.compare ? { compare: text(last.compare, 120) } : {}),
580
+ },
581
+ }
582
+ : {}),
583
+ ...(change
584
+ ? {
585
+ changes: {
586
+ appeared: this.owner
587
+ .openThreads(change.appeared, now)
588
+ .slice(0, 6)
589
+ .map(line),
590
+ faded: this.owner
591
+ .openThreads(change.faded, now)
592
+ .slice(0, 6)
593
+ .map(line),
594
+ changed: this.owner
595
+ .openThreads(change.changed, now)
596
+ .slice(0, 6)
597
+ .map(
598
+ (t) =>
599
+ `${text(t.before.content, 40)} → ${text(t.content, 40)}`,
600
+ ),
601
+ people: change.people
602
+ .sort(
603
+ (a, b) => Math.abs(b.shift ?? 1) - Math.abs(a.shift ?? 1),
604
+ )
605
+ .slice(0, 5)
606
+ .map(
607
+ (p) =>
608
+ `${p.name}:${p.shift === null ? "新认识的" : p.shift > 0 ? "更近了" : "远了一些"}`,
609
+ ),
610
+ ...(change.livingFor
611
+ ? {
612
+ livingFor: `${text(change.livingFor.from, 40) || "还没有"} → ${text(change.livingFor.to, 40) || "还没有"}`,
613
+ }
614
+ : {}),
615
+ },
616
+ }
617
+ : {}),
618
+ ...(kept.length || missed.length
619
+ ? { ahead: { kept: kept.slice(0, 5), missed: missed.slice(0, 5) } }
620
+ : {}),
621
+ ...(chapter
622
+ ? {
623
+ chapter: {
624
+ number: chapter.chapter,
625
+ title: chapter.title,
626
+ content: this.#presentLifeWriting(chapter.content, now, 800),
627
+ reviews: periods.reviewsSince(
628
+ periods.began(chapter.chapter),
629
+ now,
630
+ ),
631
+ },
632
+ }
633
+ : {}),
634
+ ...(previous
635
+ ? {
636
+ previousChapter: {
637
+ number: previous.chapter,
638
+ title: previous.title,
639
+ ...(previousWords ? { summary: previousWords } : {}),
640
+ },
641
+ }
642
+ : {}),
643
+ ...(storyWords ? { story: storyWords } : {}),
644
+ ...(anniversaries.length ? { anniversaries } : {}),
645
+ self: this.owner.selfView(now, { open: true }).slice(0, 10),
646
+ ...(this.owner.livingForView(now, { since: start, open: true })
647
+ ? {
648
+ livingFor: this.owner.livingForView(now, {
649
+ since: start,
650
+ open: true,
651
+ }),
652
+ }
653
+ : {}),
654
+ };
655
+ const result = await this.owner.chat.models.call(
656
+ this.owner.profile(),
657
+ "weekly",
658
+ replyPrompt(nature, prompts(this.owner.repo), "weekly"),
659
+ input,
660
+ trace,
661
+ );
662
+ const week = text(result?.week, 1500);
663
+ if (!week || hasCredential(week)) throw SyntaxError("回顾格式无效");
664
+ const valid = new Set([
665
+ ...diaries.map((d) => `d:${d.day}`),
666
+ ...(input.livingFor ? [`s:${input.livingFor.thread}`] : []),
667
+ ]);
668
+ periods.write("week", {
669
+ start,
670
+ end: now,
671
+ content: week,
672
+ compare: result?.compare,
673
+ sources: [...valid],
674
+ runId: id,
675
+ time: now,
676
+ });
677
+ const applied = this.owner.grow(
678
+ { self: result.self, bonds: result.bonds },
679
+ valid,
680
+ "weekly",
681
+ now,
682
+ );
683
+ const turned = this.owner.turnChapter(result?.chapter, {
684
+ chapter,
685
+ start,
686
+ now,
687
+ runId: id,
688
+ });
689
+ const told = text(result?.story, 1800);
690
+ const prior = story?.content || "";
691
+ const living = input.livingFor?.content || "";
692
+ const livingMoved = Boolean(living) && !prior.includes(living);
693
+ const retold =
694
+ !!told &&
695
+ told !== prior &&
696
+ !hasCredential(told) &&
697
+ (turned.opened || !story || livingMoved);
698
+ if (retold)
699
+ periods.write("story", {
700
+ content: told,
701
+ sources: [...valid],
702
+ runId: id,
703
+ time: now,
704
+ });
705
+ summary = { chapter: turned.number, story: retold, ...applied };
706
+ status = "written";
707
+ reason =
708
+ turned.opened && chapter
709
+ ? `回顾了这段日子,翻开了第 ${turned.number} 章`
710
+ : turned.number
711
+ ? `回顾了这段日子,写下了第 ${turned.number} 章`
712
+ : "回顾了这段日子";
713
+ } catch (error) {
714
+ status = "error";
715
+ reason =
716
+ error instanceof SyntaxError
717
+ ? "回顾格式无效,未保存"
718
+ : String(error.message).slice(0, 300);
719
+ trace.error = error.message;
720
+ } finally {
721
+ trace.reason = reason;
722
+ this.owner.chat.finishQuietly(
723
+ trace,
724
+ status === "error" ? "error" : "complete",
725
+ );
726
+ this.owner.end(id, status, reason, trace, summary);
727
+ this.owner.busy = false;
728
+ }
729
+ return { status, reason, runId: id };
730
+ }
731
+ turnChapter(value, { chapter, start, now, runId }) {
732
+ const none = { number: null, opened: false };
733
+ if (!value || typeof value !== "object") return none;
734
+ if (!["continue", "close"].includes(value.action)) return none;
735
+ const title = text(value.title, 60);
736
+ const content = text(value.content, 2400);
737
+ if (!title || !content || hasCredential(content)) return none;
738
+ const periods = this.owner.mind.periods;
739
+ if (!chapter)
740
+ return {
741
+ number: periods.writeChapter({
742
+ number: 1,
743
+ title,
744
+ content,
745
+ start: this.owner.mind.days.born() ?? start,
746
+ end: now,
747
+ runId,
748
+ time: now,
749
+ }),
750
+ opened: true,
751
+ };
752
+ if (value.action === "continue")
753
+ return {
754
+ number: periods.writeChapter({
755
+ number: chapter.chapter,
756
+ title,
757
+ content,
758
+ start: periods.began(chapter.chapter) ?? chapter.period_start,
759
+ end: now,
760
+ runId,
761
+ time: now,
762
+ }),
763
+ opened: false,
764
+ };
765
+ return {
766
+ number: periods.writeChapter({
767
+ number: chapter.chapter + 1,
768
+ title,
769
+ content,
770
+ start: now,
771
+ end: now,
772
+ runId,
773
+ time: now,
774
+ }),
775
+ opened: true,
776
+ };
777
+ }
778
+ diaries({ before = "9999-12-31", limit = 14 } = {}) {
779
+ return this.owner.db
780
+ .prepare(
781
+ "SELECT * FROM mind_diary WHERE day<=? ORDER BY day DESC, created DESC LIMIT ?",
782
+ )
783
+ .all(before, limit)
784
+ .map((d) => ({ ...d, sources: parse(d.sources, []) }));
785
+ }
786
+ }