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
package/server/mind/own-day.js
CHANGED
|
@@ -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
|
+
}
|