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.
- package/.github/SECURITY.md +4 -4
- package/README.en.md +252 -250
- package/README.md +252 -250
- package/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +8 -0
- package/docs/en/connect.md +18 -18
- package/docs/en/guide.md +202 -216
- package/docs/en/npm-release.md +25 -25
- package/docs/zh/CHANGELOG.md +8 -0
- package/docs/zh/connect.md +17 -17
- package/docs/zh/guide.md +202 -216
- package/docs/zh/npm-release.md +33 -33
- package/package.json +110 -108
- package/plugins/sdk/index.d.ts +155 -155
- package/plugins/sdk/testing.js +58 -58
- package/plugins/weather/index.js +82 -82
- package/plugins/weather/luckytri-plugin.json +29 -29
- package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
- package/public/app/assets/Empty-DP6WMGHi.js +2 -0
- package/public/app/assets/Field-CtbdqaiB.js +1 -0
- package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
- package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
- package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
- package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
- package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
- package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
- package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
- package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
- package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
- package/public/app/assets/TimePage-7-awH5XL.js +3 -0
- package/public/app/assets/index-4rItIkD-.js +4 -0
- package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
- package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
- package/public/app/assets/media-SMfTT-yc.js +1 -0
- package/public/app/index.html +3 -3
- package/public/guide.en.html +72 -99
- package/public/guide.html +44 -86
- package/public/tutorial.en.md +25 -39
- package/public/tutorial.md +25 -39
- package/scripts/backup.js +311 -311
- package/scripts/browser.js +41 -41
- package/scripts/import-data.js +128 -0
- package/scripts/launch.js +101 -94
- package/scripts/plugin.js +150 -150
- package/scripts/reset-password.js +22 -0
- package/scripts/runtime.js +25 -25
- package/scripts/service.js +56 -47
- package/scripts/setup.js +50 -50
- package/scripts/start-server.js +41 -41
- package/scripts/stop.js +36 -36
- package/server/app.js +112 -96
- package/server/auth.js +217 -0
- package/server/channels/contract.js +57 -57
- package/server/channels/hub.js +165 -165
- package/server/channels/index.js +28 -25
- package/server/channels/onebot/index.js +335 -336
- package/server/channels/onebot/token.js +22 -3
- package/server/core/capabilities.js +208 -208
- package/server/core/events.js +64 -60
- package/server/core/image-preprocessor.js +208 -208
- package/server/core/model-manager.js +1213 -1213
- package/server/core/net-guard.js +49 -49
- package/server/core/orchestrator.js +569 -569
- package/server/core/perceivers.js +91 -91
- package/server/core/persona-manager.js +243 -243
- package/server/core/repository.js +293 -293
- package/server/core/response-validator.js +508 -508
- package/server/core/turn-processor.js +630 -630
- package/server/core/turn.js +236 -236
- package/server/core/vision-manager.js +633 -633
- package/server/i18n/errors.js +548 -497
- package/server/i18n/labels.js +649 -653
- package/server/index.js +151 -102
- package/server/knowledge/manager.js +386 -386
- package/server/knowledge/schema.js +12 -8
- package/server/local-access.js +32 -0
- package/server/mind/anticipations.js +507 -507
- package/server/mind/api.js +703 -703
- package/server/mind/budget.js +152 -152
- package/server/mind/context-selection.js +61 -61
- package/server/mind/day-planner.js +324 -324
- package/server/mind/evidence.js +148 -148
- package/server/mind/index.js +445 -445
- package/server/mind/life/diary.js +786 -786
- package/server/mind/life/index.js +1049 -1049
- package/server/mind/observations.js +84 -84
- package/server/mind/own-day.js +324 -324
- package/server/mind/own-voice.js +318 -318
- package/server/mind/senses.js +53 -53
- package/server/mind/time/activity-clock.js +196 -196
- package/server/mind/time/availability.js +69 -69
- package/server/mind/time/commitment-review.js +198 -198
- package/server/mind/time/executor.js +512 -512
- package/server/mind/time/experiences.js +128 -128
- package/server/mind/time/games.js +562 -562
- package/server/mind/time/index.js +739 -739
- package/server/mind/time/intent.js +166 -166
- package/server/mind/time/kinds.js +171 -171
- package/server/mind/time/plugin-activity.js +207 -207
- package/server/mind/time/tasks.js +587 -587
- package/server/mind/util.js +110 -110
- package/server/mind/view.js +273 -273
- package/server/paths.js +102 -102
- package/server/plugins/api.js +204 -204
- package/server/plugins/host.js +967 -967
- package/server/plugins/install.js +295 -295
- package/server/plugins/manifest.js +201 -201
- package/server/plugins/permissions.js +42 -42
- package/server/plugins/runner/guard.mjs +36 -36
- package/server/plugins/runner/worker.mjs +250 -250
- package/server/plugins/schema.js +91 -91
- package/server/plugins/zip.js +112 -112
- package/server/startup-lock.js +118 -118
- package/server/storage/archive.js +242 -241
- package/server/storage/backup-cleanup-worker.js +8 -8
- package/server/storage/backup-cleanup.js +415 -414
- package/server/storage/backup-lock.js +76 -76
- package/server/storage/backup-scheduler.js +131 -131
- package/server/storage/data-package.js +298 -0
- package/server/storage/data-transfer-api.js +50 -0
- package/server/storage/data-transfer-worker.js +18 -0
- package/server/storage/data-transfer.js +415 -0
- package/server/storage/store.js +202 -199
- package/server/studio/readiness.js +181 -190
- package/server/studio/settings.js +236 -236
- package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
- package/public/app/assets/Empty-CpP5kQHH.js +0 -2
- package/public/app/assets/Field-D4gY-INU.js +0 -1
- package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
- package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
- package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
- package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
- package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
- package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
- package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
- package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
- package/public/app/assets/TimePage-Cw44inM4.js +0 -3
- package/public/app/assets/index-BheRW-1t.js +0 -4
- package/public/app/assets/media-BzPDBm7e.js +0 -1
|
@@ -1,786 +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
|
-
}
|
|
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
|
+
}
|