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,324 +1,324 @@
1
- import { withFallback } from "../core/model-manager.js";
2
- import { prompts, replyPrompt } from "../core/persona-manager.js";
3
- import { localClock } from "../core/conversation-cues.js";
4
- import { evidence, text, hasCredential } from "./util.js";
5
- import { CommitmentReview } from "./time/commitment-review.js";
6
- import { chosenSlot } from "./day-planner.js";
7
- import { activityEstimate } from "./time/activity-clock.js";
8
- import { gameTopic } from "./time/intent.js";
9
- import {
10
- isExecutable,
11
- isPluginActivity,
12
- pluginAvailable,
13
- withActivities,
14
- } from "./time/kinds.js";
15
-
16
- export class OwnDay {
17
- constructor(life) {
18
- this.life = life;
19
- this.db = life.db;
20
- this.review = new CommitmentReview(life);
21
- }
22
- state() {
23
- return this.life.repo.config("own-day", {});
24
- }
25
- save(patch) {
26
- this.life.repo.saveConfig("own-day", { ...this.state(), ...patch });
27
- }
28
- request() {
29
- this.save({ reviewRequested: true });
30
- return { queued: true };
31
- }
32
- status(now = this.life.now()) {
33
- const state = this.state();
34
- const pending = this.db
35
- .prepare(
36
- "SELECT COUNT(*) n FROM core_events WHERE role='assistant' AND seq>? AND time<=? AND seq NOT IN (SELECT seq FROM mind_unlived) AND seq NOT IN (SELECT seq FROM mind_time_commitment_reviews) AND COALESCE(json_extract(payload,'$.simulated'),0)=0 AND json_extract(payload,'$.artifact') IS NULL",
37
- )
38
- .get(state.reviewSeq || 0, now).n;
39
- const last = this.db
40
- .prepare(
41
- "SELECT finished,status,reason FROM mind_runs WHERE kind='day-care' AND started<=? ORDER BY started DESC LIMIT 1",
42
- )
43
- .get(now);
44
- return {
45
- queued: !!state.reviewRequested,
46
- working: this.working || null,
47
- reviewAt: state.reviewAt || null,
48
- planAt: state.planAt || null,
49
- pendingReplies: pending,
50
- last: last || null,
51
- };
52
- }
53
- async run(now = this.life.now()) {
54
- const life = this.life,
55
- time = life.mind.time;
56
- if (
57
- this.disabledForTest ||
58
- life.closed ||
59
- life.busy ||
60
- !life.settings().solitude ||
61
- life.repo.store.settings().demo ||
62
- life.phase(now).key === "asleep" ||
63
- time.primary() ||
64
- !life.mind.budget.allows("inner", now)
65
- )
66
- return null;
67
- let profile;
68
- try {
69
- profile = life.profile();
70
- } catch {
71
- return null;
72
- }
73
- const state = this.state(),
74
- settings = time.settings(),
75
- reviewDue =
76
- state.reviewRequested ||
77
- now - (state.reviewAt || 0) >= settings.commitmentReviewMinutes * 60000;
78
- const ownOpen = this.db
79
- .prepare(
80
- "SELECT count(*) n FROM mind_time_tasks WHERE kind='plan' AND state IN ('todo','scheduled','doing','paused') AND json_extract(checkpoint,'$.mergedInto') IS NULL",
81
- )
82
- .get().n;
83
- const planDue =
84
- ownOpen < 3 &&
85
- now - (state.planAt || 0) >= settings.ownPlanMinutes * 60000;
86
- if (!reviewDue && !planDue) return null;
87
- return time.attention.activity(async () => {
88
- let batch = null;
89
- if (reviewDue) {
90
- for (let page = 0; page < 5; page++) {
91
- batch = this.review.batch(now);
92
- if (!batch || batch.rows.length) break;
93
- this.save({ reviewSeq: batch.through });
94
- }
95
- if (!batch || !batch.rows.length) {
96
- this.save({ reviewAt: now, reviewRequested: false });
97
- batch = null;
98
- }
99
- }
100
- if (!batch && !planDue) return null;
101
- const reviewing = !!batch,
102
- trace = life.repo.trace("__mind__", "activity"),
103
- version = life.mind.nature.version(),
104
- runId = life.run(
105
- "day-care",
106
- reviewing ? "独处回看有没有漏下约定" : "想想接下来愿意做点什么",
107
- );
108
- this.working = reviewing ? "review" : "plan";
109
- this.save(
110
- reviewing ? { reviewAt: now, reviewRequested: false } : { planAt: now },
111
- );
112
- let status = "error",
113
- reason = "";
114
- try {
115
- const projects = time.works
116
- .projects({ limit: 12 })
117
- .filter((p) => p.state === "active" && time.visible(p, "", now))
118
- .slice(0, 4);
119
- const self = life.selfView(now, { open: true }).slice(0, 8);
120
- const input = reviewing
121
- ? {
122
- review: batch.messages,
123
- existing: time.tasks
124
- .list({ limit: 80 })
125
- .filter((t) => time.visible(t, batch.session, now))
126
- .map((t) => ({
127
- id: t.id,
128
- title: t.title,
129
- state: t.state,
130
- share: t.share_state,
131
- }))
132
- .slice(0, 12),
133
- }
134
- : {
135
- clock: localClock(now, life.mind.timeZone()),
136
- self,
137
- interests: life.mind.nature.current(now).interests,
138
- affect: life.mind.affect.state(now),
139
- projects: projects.map((p) => ({
140
- id: p.id,
141
- title: p.title,
142
- kind: p.kind,
143
- summary: text(p.summary, 240),
144
- })),
145
- available: {
146
- read: life.mind.reading.unreadCount() > 0,
147
- write: true,
148
- think: true,
149
- game: !time.search.ready(now),
150
- ...pluginAvailable(),
151
- },
152
- busy: time.tasks
153
- .slots(now)
154
- .map(({ startAt, endAt, priority }) => ({
155
- startAt,
156
- endAt,
157
- priority,
158
- })),
159
- pending: time.tasks
160
- .list({ limit: 20 })
161
- .filter(
162
- (t) =>
163
- time.visible(t, "", now) &&
164
- !["done", "abandoned"].includes(t.state),
165
- )
166
- .map((t) => ({ title: t.title, state: t.state }))
167
- .slice(0, 5),
168
- };
169
- const instruction = reviewing
170
- ? '独处回看自己确实送达的话有没有漏下承诺。review.said是自己说的话,earlier是前面的真实语境。只提取明确接受的未来行动;玩笑、引用、别人的计划、作品情节、愿望和过去完成的事不算承诺。没有日期也可以有约定。已放下或已完成的事项不要复活,已经整理的事项不要重建。输出JSON {"commitments":[{"source":"m:消息seq","quote":"said中的原文片段","accepted":true,"kind":"promise|plan","activity":"write|read|think|game|unknown","title":"具体行动"}]},没有遗漏则空数组。只整理,不发送、不声称兑现。'
171
- : '这是留给自己的空隙,不需要有人先约你,也不需要新消息才有生活。根据自己的兴趣、好奇、已有作品与精力,决定想不想随手做一件有意思的小事。可以阅读、写个小场景、玩一段游戏、整理一个想法,也可以选择歇着。一次最多一件,不为填满时间而列清单,不把别人的建议冒充自己的愿望,不编造外部经历或无法执行的动作。自己决定几点开始、这次想投入多少分钟;durationMinutes是实际安排分钟,不是API耗时,不再除以加速倍数。游戏必须选定明确名字,已有项目可以接着做,不要只说“玩一会galgame”。输出JSON {"skip":false,"plan":{"activity":"write|read|think|game","title":"具体小行动","topic":"游戏名称或空","why":"自己的动机","startAt":"本地YYYY-MM-DD HH:mm","durationMinutes":20,"sources":["s:本次self中thread"],"projectId":"本次项目ID或null"}}。没有想做的事就skip:true。';
172
- const answer = await withFallback(
173
- life.chat.models,
174
- life.chat.fallbackFor(null, profile, trace),
175
- ).call(
176
- profile,
177
- "reflection",
178
- replyPrompt(
179
- life.mind.nature.current(now),
180
- { ...prompts(life.repo), activity: withActivities(instruction) },
181
- "activity",
182
- ),
183
- input,
184
- trace,
185
- );
186
- if (
187
- life.closed ||
188
- version !== life.mind.nature.version() ||
189
- time.primary()
190
- ) {
191
- status = "cancelled";
192
- return { status };
193
- }
194
- if (reviewing) {
195
- this.db.exec("SAVEPOINT commitment_care");
196
- try {
197
- const ids = this.review.apply(batch, answer, life.now());
198
- this.save({ reviewSeq: batch.through });
199
- status = "reviewed";
200
- reason = ids.length
201
- ? "把说过的约定重新理清了"
202
- : "回看过这一段,没有漏下的新约定";
203
- return { status, reason, tasks: ids };
204
- } catch (error) {
205
- this.db.exec("ROLLBACK TO commitment_care");
206
- throw error;
207
- } finally {
208
- this.db.exec("RELEASE commitment_care");
209
- }
210
- }
211
- if (answer?.skip === true) {
212
- status = "resting";
213
- reason = "这会儿想留一点空白";
214
- return { status, reason };
215
- }
216
- const plan = answer?.plan;
217
- if (
218
- !plan ||
219
- !isExecutable(plan.activity) ||
220
- !text(plan.title, 160) ||
221
- !text(plan.why, 200) ||
222
- hasCredential(plan.title + plan.why)
223
- )
224
- throw Error("自己的安排格式无效");
225
- if (
226
- (plan.activity === "read" && !input.available.read) ||
227
- (plan.activity === "game" && !input.available.game) ||
228
- (isPluginActivity(plan.activity) && !input.available[plan.activity])
229
- ) {
230
- status = "waiting";
231
- reason = "想做的事情还缺少条件";
232
- return { status, reason };
233
- }
234
- const valid = new Set(self.map((s) => `s:${s.thread}`)),
235
- sources = evidence(plan.sources).filter((s) => valid.has(s)),
236
- project = projects.find(
237
- (p) => p.id === plan.projectId && p.kind === plan.activity,
238
- );
239
- const topic =
240
- plan.activity === "game"
241
- ? text(
242
- plan.topic || gameTopic(plan.title) || project?.bible?.topic,
243
- 80,
244
- )
245
- : "";
246
- if (plan.activity === "game" && !topic)
247
- throw Error("需要自己选定要玩的游戏");
248
- const slot = chosenSlot(
249
- {
250
- ...plan,
251
- durationMinutes:
252
- plan.durationMinutes ??
253
- Math.ceil(
254
- activityEstimate(plan.activity, null, time.settings().paceSpeed)
255
- .plannedMs / 60000,
256
- ),
257
- reason: plan.why,
258
- },
259
- life,
260
- life.now(),
261
- now,
262
- );
263
- this.db.exec("SAVEPOINT own_choice");
264
- try {
265
- const wish = life.mind.self.propose(
266
- {
267
- kind: "intention",
268
- content: `我想${text(plan.title, 100)}`,
269
- sources,
270
- strength: 0.25,
271
- },
272
- { valid, origin: "solitude", time: life.now() },
273
- );
274
- if (!wish.thread) throw Error(wish.rejected || "愿望未保存");
275
- const result = time.tasks.add(
276
- {
277
- kind: "plan",
278
- activity: plan.activity,
279
- title:
280
- topic && !gameTopic(plan.title)
281
- ? `${plan.title} ·《${topic}》`
282
- : plan.title,
283
- why: plan.why,
284
- sources: [`s:${wish.thread}`, ...(project?.sources || [])],
285
- projectId: project?.id || null,
286
- origin: "own-day",
287
- },
288
- life.now(),
289
- );
290
- const id = result.id || result.duplicate;
291
- if (result.id)
292
- time.tasks.arrange(result.id, { ...slot, topic }, life.now());
293
- if (result.id)
294
- time.event(
295
- id,
296
- "own-choice",
297
- "空下来时自己想做的事",
298
- { wish: wish.thread },
299
- life.now(),
300
- );
301
- status = id ? "planned" : "empty";
302
- reason = id ? "给自己留下一件想做的事" : "这件事已经有所安排";
303
- return { status, reason, task: id };
304
- } catch (error) {
305
- this.db.exec("ROLLBACK TO own_choice");
306
- throw error;
307
- } finally {
308
- this.db.exec("RELEASE own_choice");
309
- }
310
- } catch (error) {
311
- reason = text(error.message, 160);
312
- trace.error = reason;
313
- return { status: "error", reason };
314
- } finally {
315
- this.working = null;
316
- life.chat.finishQuietly(
317
- trace,
318
- status === "error" ? "error" : "complete",
319
- );
320
- life.end(runId, status, reason, trace);
321
- }
322
- });
323
- }
324
- }
1
+ import { withFallback } from "../core/model-manager.js";
2
+ import { prompts, replyPrompt } from "../core/persona-manager.js";
3
+ import { localClock } from "../core/conversation-cues.js";
4
+ import { evidence, text, hasCredential } from "./util.js";
5
+ import { CommitmentReview } from "./time/commitment-review.js";
6
+ import { chosenSlot } from "./day-planner.js";
7
+ import { activityEstimate } from "./time/activity-clock.js";
8
+ import { gameTopic } from "./time/intent.js";
9
+ import {
10
+ isExecutable,
11
+ isPluginActivity,
12
+ pluginAvailable,
13
+ withActivities,
14
+ } from "./time/kinds.js";
15
+
16
+ export class OwnDay {
17
+ constructor(life) {
18
+ this.life = life;
19
+ this.db = life.db;
20
+ this.review = new CommitmentReview(life);
21
+ }
22
+ state() {
23
+ return this.life.repo.config("own-day", {});
24
+ }
25
+ save(patch) {
26
+ this.life.repo.saveConfig("own-day", { ...this.state(), ...patch });
27
+ }
28
+ request() {
29
+ this.save({ reviewRequested: true });
30
+ return { queued: true };
31
+ }
32
+ status(now = this.life.now()) {
33
+ const state = this.state();
34
+ const pending = this.db
35
+ .prepare(
36
+ "SELECT COUNT(*) n FROM core_events WHERE role='assistant' AND seq>? AND time<=? AND seq NOT IN (SELECT seq FROM mind_unlived) AND seq NOT IN (SELECT seq FROM mind_time_commitment_reviews) AND COALESCE(json_extract(payload,'$.simulated'),0)=0 AND json_extract(payload,'$.artifact') IS NULL",
37
+ )
38
+ .get(state.reviewSeq || 0, now).n;
39
+ const last = this.db
40
+ .prepare(
41
+ "SELECT finished,status,reason FROM mind_runs WHERE kind='day-care' AND started<=? ORDER BY started DESC LIMIT 1",
42
+ )
43
+ .get(now);
44
+ return {
45
+ queued: !!state.reviewRequested,
46
+ working: this.working || null,
47
+ reviewAt: state.reviewAt || null,
48
+ planAt: state.planAt || null,
49
+ pendingReplies: pending,
50
+ last: last || null,
51
+ };
52
+ }
53
+ async run(now = this.life.now()) {
54
+ const life = this.life,
55
+ time = life.mind.time;
56
+ if (
57
+ this.disabledForTest ||
58
+ life.closed ||
59
+ life.busy ||
60
+ !life.settings().solitude ||
61
+ life.repo.store.settings().demo ||
62
+ life.phase(now).key === "asleep" ||
63
+ time.primary() ||
64
+ !life.mind.budget.allows("inner", now)
65
+ )
66
+ return null;
67
+ let profile;
68
+ try {
69
+ profile = life.profile();
70
+ } catch {
71
+ return null;
72
+ }
73
+ const state = this.state(),
74
+ settings = time.settings(),
75
+ reviewDue =
76
+ state.reviewRequested ||
77
+ now - (state.reviewAt || 0) >= settings.commitmentReviewMinutes * 60000;
78
+ const ownOpen = this.db
79
+ .prepare(
80
+ "SELECT count(*) n FROM mind_time_tasks WHERE kind='plan' AND state IN ('todo','scheduled','doing','paused') AND json_extract(checkpoint,'$.mergedInto') IS NULL",
81
+ )
82
+ .get().n;
83
+ const planDue =
84
+ ownOpen < 3 &&
85
+ now - (state.planAt || 0) >= settings.ownPlanMinutes * 60000;
86
+ if (!reviewDue && !planDue) return null;
87
+ return time.attention.activity(async () => {
88
+ let batch = null;
89
+ if (reviewDue) {
90
+ for (let page = 0; page < 5; page++) {
91
+ batch = this.review.batch(now);
92
+ if (!batch || batch.rows.length) break;
93
+ this.save({ reviewSeq: batch.through });
94
+ }
95
+ if (!batch || !batch.rows.length) {
96
+ this.save({ reviewAt: now, reviewRequested: false });
97
+ batch = null;
98
+ }
99
+ }
100
+ if (!batch && !planDue) return null;
101
+ const reviewing = !!batch,
102
+ trace = life.repo.trace("__mind__", "activity"),
103
+ version = life.mind.nature.version(),
104
+ runId = life.run(
105
+ "day-care",
106
+ reviewing ? "独处回看有没有漏下约定" : "想想接下来愿意做点什么",
107
+ );
108
+ this.working = reviewing ? "review" : "plan";
109
+ this.save(
110
+ reviewing ? { reviewAt: now, reviewRequested: false } : { planAt: now },
111
+ );
112
+ let status = "error",
113
+ reason = "";
114
+ try {
115
+ const projects = time.works
116
+ .projects({ limit: 12 })
117
+ .filter((p) => p.state === "active" && time.visible(p, "", now))
118
+ .slice(0, 4);
119
+ const self = life.selfView(now, { open: true }).slice(0, 8);
120
+ const input = reviewing
121
+ ? {
122
+ review: batch.messages,
123
+ existing: time.tasks
124
+ .list({ limit: 80 })
125
+ .filter((t) => time.visible(t, batch.session, now))
126
+ .map((t) => ({
127
+ id: t.id,
128
+ title: t.title,
129
+ state: t.state,
130
+ share: t.share_state,
131
+ }))
132
+ .slice(0, 12),
133
+ }
134
+ : {
135
+ clock: localClock(now, life.mind.timeZone()),
136
+ self,
137
+ interests: life.mind.nature.current(now).interests,
138
+ affect: life.mind.affect.state(now),
139
+ projects: projects.map((p) => ({
140
+ id: p.id,
141
+ title: p.title,
142
+ kind: p.kind,
143
+ summary: text(p.summary, 240),
144
+ })),
145
+ available: {
146
+ read: life.mind.reading.unreadCount() > 0,
147
+ write: true,
148
+ think: true,
149
+ game: !time.search.ready(now),
150
+ ...pluginAvailable(),
151
+ },
152
+ busy: time.tasks
153
+ .slots(now)
154
+ .map(({ startAt, endAt, priority }) => ({
155
+ startAt,
156
+ endAt,
157
+ priority,
158
+ })),
159
+ pending: time.tasks
160
+ .list({ limit: 20 })
161
+ .filter(
162
+ (t) =>
163
+ time.visible(t, "", now) &&
164
+ !["done", "abandoned"].includes(t.state),
165
+ )
166
+ .map((t) => ({ title: t.title, state: t.state }))
167
+ .slice(0, 5),
168
+ };
169
+ const instruction = reviewing
170
+ ? '独处回看自己确实送达的话有没有漏下承诺。review.said是自己说的话,earlier是前面的真实语境。只提取明确接受的未来行动;玩笑、引用、别人的计划、作品情节、愿望和过去完成的事不算承诺。没有日期也可以有约定。已放下或已完成的事项不要复活,已经整理的事项不要重建。输出JSON {"commitments":[{"source":"m:消息seq","quote":"said中的原文片段","accepted":true,"kind":"promise|plan","activity":"write|read|think|game|unknown","title":"具体行动"}]},没有遗漏则空数组。只整理,不发送、不声称兑现。'
171
+ : '这是留给自己的空隙,不需要有人先约你,也不需要新消息才有生活。根据自己的兴趣、好奇、已有作品与精力,决定想不想随手做一件有意思的小事。可以阅读、写个小场景、玩一段游戏、整理一个想法,也可以选择歇着。一次最多一件,不为填满时间而列清单,不把别人的建议冒充自己的愿望,不编造外部经历或无法执行的动作。自己决定几点开始、这次想投入多少分钟;durationMinutes是实际安排分钟,不是API耗时,不再除以加速倍数。游戏必须选定明确名字,已有项目可以接着做,不要只说“玩一会galgame”。输出JSON {"skip":false,"plan":{"activity":"write|read|think|game","title":"具体小行动","topic":"游戏名称或空","why":"自己的动机","startAt":"本地YYYY-MM-DD HH:mm","durationMinutes":20,"sources":["s:本次self中thread"],"projectId":"本次项目ID或null"}}。没有想做的事就skip:true。';
172
+ const answer = await withFallback(
173
+ life.chat.models,
174
+ life.chat.fallbackFor(null, profile, trace),
175
+ ).call(
176
+ profile,
177
+ "reflection",
178
+ replyPrompt(
179
+ life.mind.nature.current(now),
180
+ { ...prompts(life.repo), activity: withActivities(instruction) },
181
+ "activity",
182
+ ),
183
+ input,
184
+ trace,
185
+ );
186
+ if (
187
+ life.closed ||
188
+ version !== life.mind.nature.version() ||
189
+ time.primary()
190
+ ) {
191
+ status = "cancelled";
192
+ return { status };
193
+ }
194
+ if (reviewing) {
195
+ this.db.exec("SAVEPOINT commitment_care");
196
+ try {
197
+ const ids = this.review.apply(batch, answer, life.now());
198
+ this.save({ reviewSeq: batch.through });
199
+ status = "reviewed";
200
+ reason = ids.length
201
+ ? "把说过的约定重新理清了"
202
+ : "回看过这一段,没有漏下的新约定";
203
+ return { status, reason, tasks: ids };
204
+ } catch (error) {
205
+ this.db.exec("ROLLBACK TO commitment_care");
206
+ throw error;
207
+ } finally {
208
+ this.db.exec("RELEASE commitment_care");
209
+ }
210
+ }
211
+ if (answer?.skip === true) {
212
+ status = "resting";
213
+ reason = "这会儿想留一点空白";
214
+ return { status, reason };
215
+ }
216
+ const plan = answer?.plan;
217
+ if (
218
+ !plan ||
219
+ !isExecutable(plan.activity) ||
220
+ !text(plan.title, 160) ||
221
+ !text(plan.why, 200) ||
222
+ hasCredential(plan.title + plan.why)
223
+ )
224
+ throw Error("自己的安排格式无效");
225
+ if (
226
+ (plan.activity === "read" && !input.available.read) ||
227
+ (plan.activity === "game" && !input.available.game) ||
228
+ (isPluginActivity(plan.activity) && !input.available[plan.activity])
229
+ ) {
230
+ status = "waiting";
231
+ reason = "想做的事情还缺少条件";
232
+ return { status, reason };
233
+ }
234
+ const valid = new Set(self.map((s) => `s:${s.thread}`)),
235
+ sources = evidence(plan.sources).filter((s) => valid.has(s)),
236
+ project = projects.find(
237
+ (p) => p.id === plan.projectId && p.kind === plan.activity,
238
+ );
239
+ const topic =
240
+ plan.activity === "game"
241
+ ? text(
242
+ plan.topic || gameTopic(plan.title) || project?.bible?.topic,
243
+ 80,
244
+ )
245
+ : "";
246
+ if (plan.activity === "game" && !topic)
247
+ throw Error("需要自己选定要玩的游戏");
248
+ const slot = chosenSlot(
249
+ {
250
+ ...plan,
251
+ durationMinutes:
252
+ plan.durationMinutes ??
253
+ Math.ceil(
254
+ activityEstimate(plan.activity, null, time.settings().paceSpeed)
255
+ .plannedMs / 60000,
256
+ ),
257
+ reason: plan.why,
258
+ },
259
+ life,
260
+ life.now(),
261
+ now,
262
+ );
263
+ this.db.exec("SAVEPOINT own_choice");
264
+ try {
265
+ const wish = life.mind.self.propose(
266
+ {
267
+ kind: "intention",
268
+ content: `我想${text(plan.title, 100)}`,
269
+ sources,
270
+ strength: 0.25,
271
+ },
272
+ { valid, origin: "solitude", time: life.now() },
273
+ );
274
+ if (!wish.thread) throw Error(wish.rejected || "愿望未保存");
275
+ const result = time.tasks.add(
276
+ {
277
+ kind: "plan",
278
+ activity: plan.activity,
279
+ title:
280
+ topic && !gameTopic(plan.title)
281
+ ? `${plan.title} ·《${topic}》`
282
+ : plan.title,
283
+ why: plan.why,
284
+ sources: [`s:${wish.thread}`, ...(project?.sources || [])],
285
+ projectId: project?.id || null,
286
+ origin: "own-day",
287
+ },
288
+ life.now(),
289
+ );
290
+ const id = result.id || result.duplicate;
291
+ if (result.id)
292
+ time.tasks.arrange(result.id, { ...slot, topic }, life.now());
293
+ if (result.id)
294
+ time.event(
295
+ id,
296
+ "own-choice",
297
+ "空下来时自己想做的事",
298
+ { wish: wish.thread },
299
+ life.now(),
300
+ );
301
+ status = id ? "planned" : "empty";
302
+ reason = id ? "给自己留下一件想做的事" : "这件事已经有所安排";
303
+ return { status, reason, task: id };
304
+ } catch (error) {
305
+ this.db.exec("ROLLBACK TO own_choice");
306
+ throw error;
307
+ } finally {
308
+ this.db.exec("RELEASE own_choice");
309
+ }
310
+ } catch (error) {
311
+ reason = text(error.message, 160);
312
+ trace.error = reason;
313
+ return { status: "error", reason };
314
+ } finally {
315
+ this.working = null;
316
+ life.chat.finishQuietly(
317
+ trace,
318
+ status === "error" ? "error" : "complete",
319
+ );
320
+ life.end(runId, status, reason, trace);
321
+ }
322
+ });
323
+ }
324
+ }