luckytri 1.0.0 → 1.0.2

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 (139) hide show
  1. package/.github/SECURITY.md +4 -4
  2. package/README.en.md +252 -250
  3. package/README.md +252 -250
  4. package/bin/luckytri.js +5 -1
  5. package/docs/en/CHANGELOG.md +8 -0
  6. package/docs/en/connect.md +18 -18
  7. package/docs/en/guide.md +202 -216
  8. package/docs/en/npm-release.md +25 -25
  9. package/docs/zh/CHANGELOG.md +8 -0
  10. package/docs/zh/connect.md +17 -17
  11. package/docs/zh/guide.md +202 -216
  12. package/docs/zh/npm-release.md +33 -33
  13. package/package.json +110 -108
  14. package/plugins/sdk/index.d.ts +155 -155
  15. package/plugins/sdk/testing.js +58 -58
  16. package/plugins/weather/index.js +82 -82
  17. package/plugins/weather/luckytri-plugin.json +29 -29
  18. package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
  19. package/public/app/assets/Empty-DP6WMGHi.js +2 -0
  20. package/public/app/assets/Field-CtbdqaiB.js +1 -0
  21. package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
  22. package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
  23. package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
  24. package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
  25. package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
  26. package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
  27. package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
  28. package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
  29. package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
  30. package/public/app/assets/TimePage-7-awH5XL.js +3 -0
  31. package/public/app/assets/index-4rItIkD-.js +4 -0
  32. package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
  33. package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
  34. package/public/app/assets/media-SMfTT-yc.js +1 -0
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +72 -99
  37. package/public/guide.html +44 -86
  38. package/public/tutorial.en.md +25 -39
  39. package/public/tutorial.md +25 -39
  40. package/scripts/backup.js +311 -311
  41. package/scripts/browser.js +41 -41
  42. package/scripts/import-data.js +128 -0
  43. package/scripts/launch.js +101 -94
  44. package/scripts/plugin.js +150 -150
  45. package/scripts/reset-password.js +22 -0
  46. package/scripts/runtime.js +25 -25
  47. package/scripts/service.js +56 -47
  48. package/scripts/setup.js +50 -50
  49. package/scripts/start-server.js +41 -41
  50. package/scripts/stop.js +36 -36
  51. package/server/app.js +112 -96
  52. package/server/auth.js +217 -0
  53. package/server/channels/contract.js +57 -57
  54. package/server/channels/hub.js +165 -165
  55. package/server/channels/index.js +28 -25
  56. package/server/channels/onebot/index.js +335 -336
  57. package/server/channels/onebot/token.js +22 -3
  58. package/server/core/capabilities.js +208 -208
  59. package/server/core/events.js +64 -60
  60. package/server/core/image-preprocessor.js +208 -208
  61. package/server/core/model-manager.js +1213 -1213
  62. package/server/core/net-guard.js +49 -49
  63. package/server/core/orchestrator.js +569 -569
  64. package/server/core/perceivers.js +91 -91
  65. package/server/core/persona-manager.js +243 -243
  66. package/server/core/repository.js +293 -293
  67. package/server/core/response-validator.js +508 -508
  68. package/server/core/turn-processor.js +630 -630
  69. package/server/core/turn.js +236 -236
  70. package/server/core/vision-manager.js +633 -633
  71. package/server/i18n/errors.js +548 -497
  72. package/server/i18n/labels.js +649 -653
  73. package/server/index.js +151 -102
  74. package/server/knowledge/manager.js +386 -386
  75. package/server/knowledge/schema.js +12 -8
  76. package/server/local-access.js +32 -0
  77. package/server/mind/anticipations.js +507 -507
  78. package/server/mind/api.js +703 -703
  79. package/server/mind/budget.js +152 -152
  80. package/server/mind/context-selection.js +61 -61
  81. package/server/mind/day-planner.js +324 -324
  82. package/server/mind/evidence.js +148 -148
  83. package/server/mind/index.js +445 -445
  84. package/server/mind/life/diary.js +786 -786
  85. package/server/mind/life/index.js +1049 -1049
  86. package/server/mind/observations.js +84 -84
  87. package/server/mind/own-day.js +324 -324
  88. package/server/mind/own-voice.js +318 -318
  89. package/server/mind/senses.js +53 -53
  90. package/server/mind/time/activity-clock.js +196 -196
  91. package/server/mind/time/availability.js +69 -69
  92. package/server/mind/time/commitment-review.js +198 -198
  93. package/server/mind/time/executor.js +512 -512
  94. package/server/mind/time/experiences.js +128 -128
  95. package/server/mind/time/games.js +562 -562
  96. package/server/mind/time/index.js +739 -739
  97. package/server/mind/time/intent.js +166 -166
  98. package/server/mind/time/kinds.js +171 -171
  99. package/server/mind/time/plugin-activity.js +207 -207
  100. package/server/mind/time/tasks.js +587 -587
  101. package/server/mind/util.js +110 -110
  102. package/server/mind/view.js +273 -273
  103. package/server/paths.js +102 -102
  104. package/server/plugins/api.js +204 -204
  105. package/server/plugins/host.js +967 -967
  106. package/server/plugins/install.js +295 -295
  107. package/server/plugins/manifest.js +201 -201
  108. package/server/plugins/permissions.js +42 -42
  109. package/server/plugins/runner/guard.mjs +36 -36
  110. package/server/plugins/runner/worker.mjs +250 -250
  111. package/server/plugins/schema.js +91 -91
  112. package/server/plugins/zip.js +112 -112
  113. package/server/startup-lock.js +118 -118
  114. package/server/storage/archive.js +242 -241
  115. package/server/storage/backup-cleanup-worker.js +8 -8
  116. package/server/storage/backup-cleanup.js +415 -414
  117. package/server/storage/backup-lock.js +76 -76
  118. package/server/storage/backup-scheduler.js +131 -131
  119. package/server/storage/data-package.js +298 -0
  120. package/server/storage/data-transfer-api.js +50 -0
  121. package/server/storage/data-transfer-worker.js +18 -0
  122. package/server/storage/data-transfer.js +415 -0
  123. package/server/storage/store.js +202 -199
  124. package/server/studio/readiness.js +181 -190
  125. package/server/studio/settings.js +236 -236
  126. package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
  127. package/public/app/assets/Empty-CpP5kQHH.js +0 -2
  128. package/public/app/assets/Field-D4gY-INU.js +0 -1
  129. package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
  130. package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
  131. package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
  132. package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
  133. package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
  134. package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
  135. package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
  136. package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
  137. package/public/app/assets/TimePage-Cw44inM4.js +0 -3
  138. package/public/app/assets/index-BheRW-1t.js +0 -4
  139. package/public/app/assets/media-BzPDBm7e.js +0 -1
@@ -1,273 +1,273 @@
1
- import { summarizeGroupStyle } from "../core/group-style.js";
2
- import { relevantContext, boundMindContext } from "./context-selection.js";
3
- import { interestTerms } from "./attention.js";
4
- import { agoLabel, elapsedLabel } from "./clock.js";
5
- import { describeFace } from "./faces.js";
6
- import { SELF_KINDS } from "./self.js";
7
- import { DAY, text } from "./util.js";
8
-
9
- const FORGOTTEN = "(很久没想起了)";
10
-
11
- // One person's own words. Two people cannot be added together, and a line
12
- // she already said does not count. A bare string is one utterance.
13
- function speakerCues(cue) {
14
- const groups = new Map();
15
- let anon = 0;
16
- for (const item of cue || []) {
17
- if (item && typeof item === "object") {
18
- if (item.role === "assistant") continue;
19
- const id = String(item.userId || item.speaker || "");
20
- if (!id) continue;
21
- const texts = groups.get(id) || [];
22
- texts.push(String(item.text || ""));
23
- groups.set(id, texts);
24
- } else groups.set(`\0${anon++}`, [String(item ?? "")]);
25
- }
26
- return [...groups.values()]
27
- .map((texts) => interestTerms(texts))
28
- .filter((terms) => terms.size);
29
- }
30
-
31
- const FEEDBACK = {
32
- too_long: "太长了",
33
- too_formal: "太端着",
34
- too_meme: "梗太多",
35
- natural: "挺自然",
36
- };
37
-
38
- // The way a room talks is read as of `now`, not as of the wall clock: a replay
39
- // or a test that moves the clock must see the room as it was at that moment.
40
- function groupStyle(mind, session, now) {
41
- try {
42
- const rows = mind.db
43
- .prepare(
44
- "SELECT * FROM (SELECT * FROM messages WHERE session_id=? AND is_demo=0 AND time<=? ORDER BY id DESC LIMIT 200) ORDER BY id",
45
- )
46
- .all(session, now + 60000);
47
- const ids = rows.map((row) => row.event_id).filter(Boolean);
48
- const away = ids.length
49
- ? new Set(
50
- mind.db
51
- .prepare(
52
- `SELECT e.event_id FROM core_events e JOIN mind_unlived u ON u.seq=e.seq WHERE e.event_id IN (${ids.map(() => "?").join(",")})`,
53
- )
54
- .all(...ids)
55
- .map((row) => row.event_id),
56
- )
57
- : new Set();
58
- const profile = summarizeGroupStyle(
59
- away.size ? rows.filter((row) => !away.has(row.event_id)) : rows,
60
- now,
61
- );
62
- return profile.ready ? profile.summary : "";
63
- } catch {
64
- return "";
65
- }
66
- }
67
-
68
- function feedback(mind, session, now) {
69
- return mind.db
70
- .prepare(
71
- "SELECT f.tag,d.reply FROM reply_feedback f JOIN decisions d ON d.id=f.decision_id WHERE d.session_id=? AND d.is_demo=0 AND f.time>? AND f.time<=? ORDER BY f.time DESC LIMIT 2",
72
- )
73
- .all(session, now - 3 * DAY, now)
74
- .filter((row) => FEEDBACK[row.tag] && row.tag !== "natural")
75
- .map(
76
- (row) => `有人觉得我说的「${text(row.reply, 30)}」${FEEDBACK[row.tag]}`,
77
- );
78
- }
79
-
80
- // Who she is right now, in a few hundred tokens. `self` changes a few times a
81
- // day and sits in the cached head of the prompt; `inner` changes every turn,
82
- // so anything this batch of messages brings back to mind belongs there.
83
- export function innerView(
84
- mind,
85
- { session, kind = "group", people = [], cue = [], now = Date.now() },
86
- ) {
87
- const nature = mind.nature.current(now);
88
- const affect = mind.affect.state(now, { nature, room: session });
89
- const face = mind.faces.current(session, now);
90
- const living = mind.self.living({ before: now, now, room: session });
91
- const cues = speakerCues(cue);
92
- const carries = (thread) => mind.meetings.stays(thread, session, now);
93
- const visibleThreads = relevantContext(
94
- mind.self
95
- .annotated({ before: now, now })
96
- .filter((t) => !t.faded && carries(t)),
97
- { cues, limit: 6, pinned: (t) => t.core || t.thread === living?.thread },
98
- );
99
- const shownLiving = living && carries(living) ? living : null;
100
- const reminded = [
101
- ...mind.self
102
- .reminded({ before: now, now, cues, limit: 2 })
103
- .filter(carries)
104
- .map((t) => `${SELF_KINDS[t.kind]}:${text(t.content, 80)}${FORGOTTEN}`),
105
- ...mind.thoughts
106
- .reminded({ now, cues, session, limit: 1 })
107
- .filter((t) => mind.meetings.sayable(t.content, session))
108
- .map(
109
- (t) =>
110
- `${elapsedLabel(t.created, now, mind.timeZone())}想到:${text(t.content, 110)}${FORGOTTEN}`,
111
- ),
112
- ...mind.meetings
113
- .reminded({ session, now, cues, limit: 1 })
114
- .filter((line) => mind.meetings.sayable(line, session))
115
- .map((line) => `${line}${FORGOTTEN}`),
116
- ];
117
- const diary = mind.db
118
- .prepare(
119
- "SELECT day,content FROM mind_diary WHERE created<=? ORDER BY created DESC LIMIT 1",
120
- )
121
- .get(now);
122
- const diaryHere =
123
- diary &&
124
- !mind.meetings.privateBeyond(diary.day, session, now) &&
125
- mind.meetings.sayable(diary.content, session)
126
- ? `${diary.day}:${text(diary.content, 110)}`
127
- : "";
128
- const read = mind.reading
129
- .recent({ now, limit: 2 })
130
- .filter((r) => r.created > now - 7 * DAY);
131
- const room = kind === "group" ? groupStyle(mind, session, now) : "";
132
- const spoken = (value) =>
133
- value && mind.meetings.sayable(value, session) ? value : "";
134
- const self = {
135
- ...(face
136
- ? {
137
- here: describeFace({
138
- ...face,
139
- role: spoken(face.role),
140
- tone: spoken(face.tone),
141
- aspiration: spoken(face.aspiration),
142
- content: spoken(face.content),
143
- }),
144
- }
145
- : {}),
146
- ...(visibleThreads.length
147
- ? {
148
- threads: visibleThreads.map(
149
- (t) =>
150
- `${SELF_KINDS[t.kind]}:${text(t.content, 80)}${t.status === "emerging" ? "(刚开始有这种感觉)" : ""}`,
151
- ),
152
- }
153
- : {}),
154
- ...(shownLiving ? { livingFor: text(shownLiving.content, 80) } : {}),
155
- ...(diaryHere ? { lastDiary: diaryHere } : {}),
156
- ...(read.length
157
- ? {
158
- readLately: read.map((r) => {
159
- const note =
160
- r.note && mind.meetings.sayable(r.note, session)
161
- ? text(r.note, 60)
162
- : "";
163
- return `读过《${r.title}》第 ${r.ordinal + 1} 段${note ? `:${note}` : ""}`;
164
- }),
165
- }
166
- : {}),
167
- };
168
- if (!self.here) delete self.here;
169
- const persons = [];
170
- for (const id of [...new Set(people.map(String))].slice(0, 6)) {
171
- const bond = mind.bonds.person(id, now, { room: session });
172
- // A recorded feeling counts even before anyone has spoken. Merely being
173
- // seen does not.
174
- if (
175
- !bond ||
176
- (!bond.interactions &&
177
- !bond.impression &&
178
- !bond.lastChange &&
179
- bond.tension < 0.15)
180
- )
181
- continue;
182
- persons.push({
183
- id,
184
- name: bond.name,
185
- feel: bond.feel,
186
- ...(bond.impression && mind.meetings.sayable(bond.impression, session)
187
- ? { impression: bond.impression }
188
- : {}),
189
- // Someone coming back after days away, or someone who has been around
190
- // without talking with her for a while.
191
- ...(bond.awayDays >= 3
192
- ? { away: `上次见到是 ${agoLabel(now - bond.seenAt)}` }
193
- : bond.absentDays >= 7
194
- ? { lastTalked: `上次说上话是 ${agoLabel(now - bond.lastTalkedAt)}` }
195
- : {}),
196
- });
197
- }
198
- const group = kind === "group" ? mind.bonds.group(session, now) : null;
199
- const thoughts = relevantContext(
200
- mind.thoughts
201
- .open({ now, limit: 400, session })
202
- .filter((t) => mind.meetings.sayable(t.content, session)),
203
- { cues, limit: 3 },
204
- );
205
- const heard = feedback(mind, session, now);
206
- const continuity = mind.continuity.recall({ session, people, cue, now });
207
- const expecting = mind.anticipations
208
- .upcoming({ now, people, session, limit: 3 })
209
- .map((a) => a.text);
210
- const withWhom = mind.meetings.recall({
211
- session,
212
- people,
213
- cue,
214
- now,
215
- limit: 2,
216
- });
217
- const wills = mind.meetings.traces({ session, before: now, limit: 2 });
218
- const will = wills
219
- .map((row) =>
220
- row.living ? row.text : `${text(row.content, 28)}:${row.text}`,
221
- )
222
- .join(";");
223
- const stood = mind.meetings.stood({
224
- session,
225
- people,
226
- cue,
227
- now,
228
- limit: 2,
229
- });
230
- const cause =
231
- affect.cause && mind.meetings.sayable(affect.cause, session)
232
- ? text(affect.cause, 40)
233
- : "";
234
- const withLines = withWhom.filter((line) =>
235
- mind.meetings.sayable(line, session),
236
- );
237
- const expectingLines = expecting.filter((line) =>
238
- mind.meetings.sayable(line, session),
239
- );
240
- const senses = mind.senses?.lines({ kind, now }) || [];
241
- const body = (mind.body?.({ kind, now }) || [])
242
- .map((line) => text(line, 80))
243
- .filter(Boolean)
244
- .slice(0, 8);
245
- return boundMindContext({
246
- self,
247
- affect: cause ? affect : { ...affect, cause: "" },
248
- inner: {
249
- currentLife: mind.time.view({ session, now, cue }),
250
- state: `${affect.phaseLabel},精力${affect.energyLabel},心情${affect.mood}${cause ? `(${cause})` : ""}${affect.lately ? `,${affect.lately}` : ""}`,
251
- ...(persons.length ? { people: persons } : {}),
252
- ...(continuity ? { continuity } : {}),
253
- ...(group ? { thisGroup: group.feel } : {}),
254
- ...(thoughts.length
255
- ? {
256
- onMind: thoughts.map(
257
- (t) =>
258
- `${elapsedLabel(t.created, now, mind.timeZone())}想到:${text(t.content, 110)}`,
259
- ),
260
- }
261
- : {}),
262
- ...(expectingLines.length ? { expecting: expectingLines } : {}),
263
- ...(withLines.length ? { with: withLines } : {}),
264
- ...(will ? { will } : {}),
265
- ...(stood.length ? { stood } : {}),
266
- ...(reminded.length ? { reminded } : {}),
267
- ...(room ? { room } : {}),
268
- ...(heard.length ? { heard } : {}),
269
- ...(senses.length ? { senses } : {}),
270
- ...(body.length ? { body } : {}),
271
- },
272
- });
273
- }
1
+ import { summarizeGroupStyle } from "../core/group-style.js";
2
+ import { relevantContext, boundMindContext } from "./context-selection.js";
3
+ import { interestTerms } from "./attention.js";
4
+ import { agoLabel, elapsedLabel } from "./clock.js";
5
+ import { describeFace } from "./faces.js";
6
+ import { SELF_KINDS } from "./self.js";
7
+ import { DAY, text } from "./util.js";
8
+
9
+ const FORGOTTEN = "(很久没想起了)";
10
+
11
+ // One person's own words. Two people cannot be added together, and a line
12
+ // she already said does not count. A bare string is one utterance.
13
+ function speakerCues(cue) {
14
+ const groups = new Map();
15
+ let anon = 0;
16
+ for (const item of cue || []) {
17
+ if (item && typeof item === "object") {
18
+ if (item.role === "assistant") continue;
19
+ const id = String(item.userId || item.speaker || "");
20
+ if (!id) continue;
21
+ const texts = groups.get(id) || [];
22
+ texts.push(String(item.text || ""));
23
+ groups.set(id, texts);
24
+ } else groups.set(`\0${anon++}`, [String(item ?? "")]);
25
+ }
26
+ return [...groups.values()]
27
+ .map((texts) => interestTerms(texts))
28
+ .filter((terms) => terms.size);
29
+ }
30
+
31
+ const FEEDBACK = {
32
+ too_long: "太长了",
33
+ too_formal: "太端着",
34
+ too_meme: "梗太多",
35
+ natural: "挺自然",
36
+ };
37
+
38
+ // The way a room talks is read as of `now`, not as of the wall clock: a replay
39
+ // or a test that moves the clock must see the room as it was at that moment.
40
+ function groupStyle(mind, session, now) {
41
+ try {
42
+ const rows = mind.db
43
+ .prepare(
44
+ "SELECT * FROM (SELECT * FROM messages WHERE session_id=? AND is_demo=0 AND time<=? ORDER BY id DESC LIMIT 200) ORDER BY id",
45
+ )
46
+ .all(session, now + 60000);
47
+ const ids = rows.map((row) => row.event_id).filter(Boolean);
48
+ const away = ids.length
49
+ ? new Set(
50
+ mind.db
51
+ .prepare(
52
+ `SELECT e.event_id FROM core_events e JOIN mind_unlived u ON u.seq=e.seq WHERE e.event_id IN (${ids.map(() => "?").join(",")})`,
53
+ )
54
+ .all(...ids)
55
+ .map((row) => row.event_id),
56
+ )
57
+ : new Set();
58
+ const profile = summarizeGroupStyle(
59
+ away.size ? rows.filter((row) => !away.has(row.event_id)) : rows,
60
+ now,
61
+ );
62
+ return profile.ready ? profile.summary : "";
63
+ } catch {
64
+ return "";
65
+ }
66
+ }
67
+
68
+ function feedback(mind, session, now) {
69
+ return mind.db
70
+ .prepare(
71
+ "SELECT f.tag,d.reply FROM reply_feedback f JOIN decisions d ON d.id=f.decision_id WHERE d.session_id=? AND d.is_demo=0 AND f.time>? AND f.time<=? ORDER BY f.time DESC LIMIT 2",
72
+ )
73
+ .all(session, now - 3 * DAY, now)
74
+ .filter((row) => FEEDBACK[row.tag] && row.tag !== "natural")
75
+ .map(
76
+ (row) => `有人觉得我说的「${text(row.reply, 30)}」${FEEDBACK[row.tag]}`,
77
+ );
78
+ }
79
+
80
+ // Who she is right now, in a few hundred tokens. `self` changes a few times a
81
+ // day and sits in the cached head of the prompt; `inner` changes every turn,
82
+ // so anything this batch of messages brings back to mind belongs there.
83
+ export function innerView(
84
+ mind,
85
+ { session, kind = "group", people = [], cue = [], now = Date.now() },
86
+ ) {
87
+ const nature = mind.nature.current(now);
88
+ const affect = mind.affect.state(now, { nature, room: session });
89
+ const face = mind.faces.current(session, now);
90
+ const living = mind.self.living({ before: now, now, room: session });
91
+ const cues = speakerCues(cue);
92
+ const carries = (thread) => mind.meetings.stays(thread, session, now);
93
+ const visibleThreads = relevantContext(
94
+ mind.self
95
+ .annotated({ before: now, now })
96
+ .filter((t) => !t.faded && carries(t)),
97
+ { cues, limit: 6, pinned: (t) => t.core || t.thread === living?.thread },
98
+ );
99
+ const shownLiving = living && carries(living) ? living : null;
100
+ const reminded = [
101
+ ...mind.self
102
+ .reminded({ before: now, now, cues, limit: 2 })
103
+ .filter(carries)
104
+ .map((t) => `${SELF_KINDS[t.kind]}:${text(t.content, 80)}${FORGOTTEN}`),
105
+ ...mind.thoughts
106
+ .reminded({ now, cues, session, limit: 1 })
107
+ .filter((t) => mind.meetings.sayable(t.content, session))
108
+ .map(
109
+ (t) =>
110
+ `${elapsedLabel(t.created, now, mind.timeZone())}想到:${text(t.content, 110)}${FORGOTTEN}`,
111
+ ),
112
+ ...mind.meetings
113
+ .reminded({ session, now, cues, limit: 1 })
114
+ .filter((line) => mind.meetings.sayable(line, session))
115
+ .map((line) => `${line}${FORGOTTEN}`),
116
+ ];
117
+ const diary = mind.db
118
+ .prepare(
119
+ "SELECT day,content FROM mind_diary WHERE created<=? ORDER BY created DESC LIMIT 1",
120
+ )
121
+ .get(now);
122
+ const diaryHere =
123
+ diary &&
124
+ !mind.meetings.privateBeyond(diary.day, session, now) &&
125
+ mind.meetings.sayable(diary.content, session)
126
+ ? `${diary.day}:${text(diary.content, 110)}`
127
+ : "";
128
+ const read = mind.reading
129
+ .recent({ now, limit: 2 })
130
+ .filter((r) => r.created > now - 7 * DAY);
131
+ const room = kind === "group" ? groupStyle(mind, session, now) : "";
132
+ const spoken = (value) =>
133
+ value && mind.meetings.sayable(value, session) ? value : "";
134
+ const self = {
135
+ ...(face
136
+ ? {
137
+ here: describeFace({
138
+ ...face,
139
+ role: spoken(face.role),
140
+ tone: spoken(face.tone),
141
+ aspiration: spoken(face.aspiration),
142
+ content: spoken(face.content),
143
+ }),
144
+ }
145
+ : {}),
146
+ ...(visibleThreads.length
147
+ ? {
148
+ threads: visibleThreads.map(
149
+ (t) =>
150
+ `${SELF_KINDS[t.kind]}:${text(t.content, 80)}${t.status === "emerging" ? "(刚开始有这种感觉)" : ""}`,
151
+ ),
152
+ }
153
+ : {}),
154
+ ...(shownLiving ? { livingFor: text(shownLiving.content, 80) } : {}),
155
+ ...(diaryHere ? { lastDiary: diaryHere } : {}),
156
+ ...(read.length
157
+ ? {
158
+ readLately: read.map((r) => {
159
+ const note =
160
+ r.note && mind.meetings.sayable(r.note, session)
161
+ ? text(r.note, 60)
162
+ : "";
163
+ return `读过《${r.title}》第 ${r.ordinal + 1} 段${note ? `:${note}` : ""}`;
164
+ }),
165
+ }
166
+ : {}),
167
+ };
168
+ if (!self.here) delete self.here;
169
+ const persons = [];
170
+ for (const id of [...new Set(people.map(String))].slice(0, 6)) {
171
+ const bond = mind.bonds.person(id, now, { room: session });
172
+ // A recorded feeling counts even before anyone has spoken. Merely being
173
+ // seen does not.
174
+ if (
175
+ !bond ||
176
+ (!bond.interactions &&
177
+ !bond.impression &&
178
+ !bond.lastChange &&
179
+ bond.tension < 0.15)
180
+ )
181
+ continue;
182
+ persons.push({
183
+ id,
184
+ name: bond.name,
185
+ feel: bond.feel,
186
+ ...(bond.impression && mind.meetings.sayable(bond.impression, session)
187
+ ? { impression: bond.impression }
188
+ : {}),
189
+ // Someone coming back after days away, or someone who has been around
190
+ // without talking with her for a while.
191
+ ...(bond.awayDays >= 3
192
+ ? { away: `上次见到是 ${agoLabel(now - bond.seenAt)}` }
193
+ : bond.absentDays >= 7
194
+ ? { lastTalked: `上次说上话是 ${agoLabel(now - bond.lastTalkedAt)}` }
195
+ : {}),
196
+ });
197
+ }
198
+ const group = kind === "group" ? mind.bonds.group(session, now) : null;
199
+ const thoughts = relevantContext(
200
+ mind.thoughts
201
+ .open({ now, limit: 400, session })
202
+ .filter((t) => mind.meetings.sayable(t.content, session)),
203
+ { cues, limit: 3 },
204
+ );
205
+ const heard = feedback(mind, session, now);
206
+ const continuity = mind.continuity.recall({ session, people, cue, now });
207
+ const expecting = mind.anticipations
208
+ .upcoming({ now, people, session, limit: 3 })
209
+ .map((a) => a.text);
210
+ const withWhom = mind.meetings.recall({
211
+ session,
212
+ people,
213
+ cue,
214
+ now,
215
+ limit: 2,
216
+ });
217
+ const wills = mind.meetings.traces({ session, before: now, limit: 2 });
218
+ const will = wills
219
+ .map((row) =>
220
+ row.living ? row.text : `${text(row.content, 28)}:${row.text}`,
221
+ )
222
+ .join(";");
223
+ const stood = mind.meetings.stood({
224
+ session,
225
+ people,
226
+ cue,
227
+ now,
228
+ limit: 2,
229
+ });
230
+ const cause =
231
+ affect.cause && mind.meetings.sayable(affect.cause, session)
232
+ ? text(affect.cause, 40)
233
+ : "";
234
+ const withLines = withWhom.filter((line) =>
235
+ mind.meetings.sayable(line, session),
236
+ );
237
+ const expectingLines = expecting.filter((line) =>
238
+ mind.meetings.sayable(line, session),
239
+ );
240
+ const senses = mind.senses?.lines({ kind, now }) || [];
241
+ const body = (mind.body?.({ kind, now }) || [])
242
+ .map((line) => text(line, 80))
243
+ .filter(Boolean)
244
+ .slice(0, 8);
245
+ return boundMindContext({
246
+ self,
247
+ affect: cause ? affect : { ...affect, cause: "" },
248
+ inner: {
249
+ currentLife: mind.time.view({ session, now, cue }),
250
+ state: `${affect.phaseLabel},精力${affect.energyLabel},心情${affect.mood}${cause ? `(${cause})` : ""}${affect.lately ? `,${affect.lately}` : ""}`,
251
+ ...(persons.length ? { people: persons } : {}),
252
+ ...(continuity ? { continuity } : {}),
253
+ ...(group ? { thisGroup: group.feel } : {}),
254
+ ...(thoughts.length
255
+ ? {
256
+ onMind: thoughts.map(
257
+ (t) =>
258
+ `${elapsedLabel(t.created, now, mind.timeZone())}想到:${text(t.content, 110)}`,
259
+ ),
260
+ }
261
+ : {}),
262
+ ...(expectingLines.length ? { expecting: expectingLines } : {}),
263
+ ...(withLines.length ? { with: withLines } : {}),
264
+ ...(will ? { will } : {}),
265
+ ...(stood.length ? { stood } : {}),
266
+ ...(reminded.length ? { reminded } : {}),
267
+ ...(room ? { room } : {}),
268
+ ...(heard.length ? { heard } : {}),
269
+ ...(senses.length ? { senses } : {}),
270
+ ...(body.length ? { body } : {}),
271
+ },
272
+ });
273
+ }