luckytri 1.0.0 → 1.0.1

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 (133) 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 +192 -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 +192 -216
  12. package/docs/zh/npm-release.md +33 -33
  13. package/package.json +109 -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-B9Odvzfz.js +3 -0
  19. package/public/app/assets/Empty-SkA8PyMo.js +2 -0
  20. package/public/app/assets/Field-BjLhi8pL.js +1 -0
  21. package/public/app/assets/HeartPage-CscKqMsr.js +1 -0
  22. package/public/app/assets/LifePage-BHCmBHlH.js +1 -0
  23. package/public/app/assets/MemoryPage-Bhs2xa1k.js +1 -0
  24. package/public/app/assets/NaturePage-CaPIar_5.js +3 -0
  25. package/public/app/assets/PeoplePage-CuwSSVeX.js +1 -0
  26. package/public/app/assets/PluginsPage-BhVm1SUi.js +2 -0
  27. package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
  28. package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-5yYqyUgR.css} +1 -1
  29. package/public/app/assets/SystemPage-Chm5fiGP.js +1 -0
  30. package/public/app/assets/TimePage-BUm4eUzB.js +3 -0
  31. package/public/app/assets/index-BIAdfObg.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-DNDLyN9m.js} +1 -1
  34. package/public/app/assets/media-CKVAvML2.js +1 -0
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +32 -99
  37. package/public/guide.html +21 -86
  38. package/public/tutorial.en.md +15 -39
  39. package/public/tutorial.md +15 -39
  40. package/scripts/backup.js +311 -311
  41. package/scripts/browser.js +41 -41
  42. package/scripts/launch.js +101 -94
  43. package/scripts/plugin.js +150 -150
  44. package/scripts/reset-password.js +22 -0
  45. package/scripts/runtime.js +25 -25
  46. package/scripts/service.js +56 -47
  47. package/scripts/setup.js +50 -50
  48. package/scripts/start-server.js +41 -41
  49. package/scripts/stop.js +36 -36
  50. package/server/app.js +110 -96
  51. package/server/auth.js +217 -0
  52. package/server/channels/contract.js +57 -57
  53. package/server/channels/hub.js +165 -165
  54. package/server/channels/index.js +28 -25
  55. package/server/channels/onebot/index.js +335 -336
  56. package/server/channels/onebot/token.js +22 -3
  57. package/server/core/capabilities.js +208 -208
  58. package/server/core/events.js +64 -60
  59. package/server/core/image-preprocessor.js +208 -208
  60. package/server/core/model-manager.js +1213 -1213
  61. package/server/core/net-guard.js +49 -49
  62. package/server/core/orchestrator.js +569 -569
  63. package/server/core/perceivers.js +91 -91
  64. package/server/core/persona-manager.js +243 -243
  65. package/server/core/repository.js +293 -293
  66. package/server/core/response-validator.js +508 -508
  67. package/server/core/turn-processor.js +630 -630
  68. package/server/core/turn.js +236 -236
  69. package/server/core/vision-manager.js +633 -633
  70. package/server/i18n/errors.js +512 -497
  71. package/server/i18n/labels.js +649 -653
  72. package/server/index.js +107 -102
  73. package/server/knowledge/manager.js +386 -386
  74. package/server/local-access.js +32 -0
  75. package/server/mind/anticipations.js +507 -507
  76. package/server/mind/api.js +703 -703
  77. package/server/mind/budget.js +152 -152
  78. package/server/mind/context-selection.js +61 -61
  79. package/server/mind/day-planner.js +324 -324
  80. package/server/mind/evidence.js +148 -148
  81. package/server/mind/index.js +445 -445
  82. package/server/mind/life/diary.js +786 -786
  83. package/server/mind/life/index.js +1049 -1049
  84. package/server/mind/observations.js +84 -84
  85. package/server/mind/own-day.js +324 -324
  86. package/server/mind/own-voice.js +318 -318
  87. package/server/mind/senses.js +53 -53
  88. package/server/mind/time/activity-clock.js +196 -196
  89. package/server/mind/time/availability.js +69 -69
  90. package/server/mind/time/commitment-review.js +198 -198
  91. package/server/mind/time/executor.js +512 -512
  92. package/server/mind/time/experiences.js +128 -128
  93. package/server/mind/time/games.js +562 -562
  94. package/server/mind/time/index.js +739 -739
  95. package/server/mind/time/intent.js +166 -166
  96. package/server/mind/time/kinds.js +171 -171
  97. package/server/mind/time/plugin-activity.js +207 -207
  98. package/server/mind/time/tasks.js +587 -587
  99. package/server/mind/util.js +110 -110
  100. package/server/mind/view.js +273 -273
  101. package/server/paths.js +102 -102
  102. package/server/plugins/api.js +204 -204
  103. package/server/plugins/host.js +967 -967
  104. package/server/plugins/install.js +295 -295
  105. package/server/plugins/manifest.js +201 -201
  106. package/server/plugins/permissions.js +42 -42
  107. package/server/plugins/runner/guard.mjs +36 -36
  108. package/server/plugins/runner/worker.mjs +250 -250
  109. package/server/plugins/schema.js +91 -91
  110. package/server/plugins/zip.js +112 -112
  111. package/server/startup-lock.js +118 -118
  112. package/server/storage/archive.js +241 -241
  113. package/server/storage/backup-cleanup-worker.js +8 -8
  114. package/server/storage/backup-cleanup.js +414 -414
  115. package/server/storage/backup-lock.js +76 -76
  116. package/server/storage/backup-scheduler.js +131 -131
  117. package/server/storage/store.js +199 -199
  118. package/server/studio/readiness.js +181 -190
  119. package/server/studio/settings.js +236 -236
  120. package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
  121. package/public/app/assets/Empty-CpP5kQHH.js +0 -2
  122. package/public/app/assets/Field-D4gY-INU.js +0 -1
  123. package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
  124. package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
  125. package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
  126. package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
  127. package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
  128. package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
  129. package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
  130. package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
  131. package/public/app/assets/TimePage-Cw44inM4.js +0 -3
  132. package/public/app/assets/index-BheRW-1t.js +0 -4
  133. package/public/app/assets/media-BzPDBm7e.js +0 -1
@@ -1,512 +1,512 @@
1
- import { randomUUID } from "node:crypto";
2
- import { localClock } from "../../core/conversation-cues.js";
3
- import { withFallback } from "../../core/model-manager.js";
4
- import { prompts, replyPrompt } from "../../core/persona-manager.js";
5
- import { evidence, hasCredential, text } from "../util.js";
6
- import { ACTIVITY_LABELS } from "./tasks.js";
7
- import { activityLabel, isPluginActivity, needsEnergy } from "./kinds.js";
8
- import { stepPlugin } from "./plugin-activity.js";
9
- import { executionBlock } from "./availability.js";
10
-
11
- const ACTIVITY_PROMPT =
12
- '执行一个属于自己的实际活动步骤。write创作一个完整小段,已有草稿时只追加下一段,修改模式则提供修改后的正文;根据篇幅决定本篇是否结束,连载完成的是本章。read只能阅读input.reading实际提供的段落;think留下想法记录。不编造外部操作、游玩、通关或未提供的资料。虚构人物只属于作品,不能进入真实人物记忆。不输出隐藏推理。外部suggestion是别人的建议,你决定采纳或拒绝并保留理由,可以改变方向、暂停或完结项目。输出JSON {"done":false,"title":"标题","content":"最多1800字完整小段","summary":"情节摘要","next":"续接位置","bible":{"characters":"人物设定","world":"世界设定","threads":"未解决线索"},"feeling":{"feeling":"两三个字的心情","valence":0.2},"accepted":true,"reason":"自己的选择理由","projectState":"active|paused|complete","share":{"choice":"send|later|decline","reason":"分享选择"}}。完成本篇时done:true;休息/拒绝时没有content并说明reason。本步骤不发消息、不直接改变人格。';
13
-
14
- export class TimeExecutor {
15
- constructor(life) {
16
- this.life = life;
17
- this.time = life.mind.time;
18
- this.db = life.db;
19
- }
20
- run(now = this.life.now()) {
21
- return this.time.attention.activity(() => this.step(now));
22
- }
23
- async step(now) {
24
- const life = this.life,
25
- mind = life.mind,
26
- time = this.time;
27
- time.tasks.sync(now);
28
- if (
29
- life.closed ||
30
- life.busy ||
31
- life.repo.store.settings().demo ||
32
- !life.settings().solitude ||
33
- life.phase(now).key === "asleep"
34
- )
35
- return null;
36
- let task, chunk;
37
- let primary = time.primary();
38
- if (primary && !primary.lease) {
39
- const urgent = time.tasks
40
- .ready(now)
41
- .find(
42
- (t) =>
43
- t.priority > primary.priority &&
44
- !executionBlock(time, t, now) &&
45
- (time.fixtureImmediate ||
46
- life.planner.disabledForTest ||
47
- (t.checkpoint.schedule?.chosenAt && !t.checkpoint.reschedule)),
48
- );
49
- if (urgent) {
50
- time.tasks.control(
51
- primary.id,
52
- {
53
- action: "pause",
54
- readyAt: now + 60000,
55
- reason: "先处理一件优先级更高的事,保留续接位置",
56
- },
57
- now,
58
- );
59
- time.event(
60
- primary.id,
61
- "preempted",
62
- "更高优先级事项先做",
63
- { forTask: urgent.id, checkpoint: primary.checkpoint.next || "" },
64
- now,
65
- );
66
- primary = null;
67
- }
68
- }
69
- for (const candidate of primary ? [primary] : time.tasks.ready(now)) {
70
- if (candidate.next_step > now) continue;
71
- if (
72
- !time.fixtureImmediate &&
73
- !life.planner.disabledForTest &&
74
- (!candidate.checkpoint.schedule?.chosenAt ||
75
- candidate.checkpoint.reschedule)
76
- )
77
- continue;
78
- const block = executionBlock(time, candidate, now);
79
- if (
80
- block &&
81
- block !== "来源已经撤销" &&
82
- !(candidate.activity === "read" && block === "书架没有可读资料")
83
- ) {
84
- time.tasks.wait(
85
- candidate,
86
- block,
87
- /选定|想具体|执行能力/.test(block)
88
- ? Number.MAX_SAFE_INTEGER
89
- : now + 10 * 60000,
90
- now,
91
- );
92
- continue;
93
- }
94
- if (
95
- candidate.checkpoint.pendingStep?.chunk &&
96
- !this.db
97
- .prepare("SELECT 1 FROM core_chunks WHERE id=?")
98
- .get(candidate.checkpoint.pendingStep.chunk.id)
99
- ) {
100
- time.tasks.wait(
101
- candidate,
102
- "书架这段内容已不可用",
103
- Number.MAX_SAFE_INTEGER,
104
- now,
105
- );
106
- continue;
107
- }
108
- if (!time.allowed(candidate, now)) {
109
- time.tasks.control(
110
- candidate.id,
111
- { action: "abandon", reason: "来源已经撤销" },
112
- now,
113
- );
114
- continue;
115
- }
116
- if (
117
- candidate.session_id &&
118
- !life.chat.enabled(candidate.session_id, { simulated: false })
119
- ) {
120
- time.tasks.wait(candidate, "来源会话目前未启用", now + 60000, now);
121
- continue;
122
- }
123
- if (
124
- !candidate.checkpoint.pendingStep &&
125
- !mind.budget.allows("inner", now)
126
- ) {
127
- time.tasks.wait(candidate, "今日独处预算不足", now + 3600000, now);
128
- continue;
129
- }
130
- if (
131
- needsEnergy(candidate.activity) &&
132
- mind.affect.state(now).energy < 0.18
133
- ) {
134
- time.tasks.wait(
135
- candidate,
136
- "精力较低,先恢复一会儿再投入",
137
- now + 10 * 60000,
138
- now,
139
- );
140
- continue;
141
- }
142
- if (candidate.activity === "game") {
143
- const blocked = candidate.checkpoint.sourceIds?.length
144
- ? ""
145
- : time.search.ready(now);
146
- if (blocked) {
147
- time.tasks.wait(candidate, blocked, now + 60000, now);
148
- continue;
149
- }
150
- }
151
- chunk =
152
- candidate.checkpoint.pendingStep?.chunk ||
153
- (candidate.activity === "read" ? mind.reading.next(now) : null);
154
- if (candidate.activity === "read" && !chunk) {
155
- time.tasks.wait(candidate, "书架没有可读资料", now, now);
156
- continue;
157
- }
158
- task = candidate;
159
- break;
160
- }
161
- if (!task) return time.sharing.run(life, now);
162
- let profile;
163
- try {
164
- if (!task.checkpoint.pendingStep) profile = life.profile();
165
- } catch {
166
- time.tasks.wait(task, "等待可用模型", now + 60000, now);
167
- return null;
168
- }
169
- task = time.start(task, now);
170
- if (!task) return null;
171
- const runId = life.run(
172
- "activity",
173
- `${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "活动")}:${task.title}`,
174
- ),
175
- trace = life.repo.trace("__mind__", "activity"),
176
- version = mind.nature.version();
177
- let status = "empty",
178
- reason = "这次还不想做",
179
- creation = null;
180
- try {
181
- if (task.activity === "game") {
182
- const outcome = await time.games.step(task, life, trace, runId);
183
- status = outcome.status;
184
- reason = outcome.reason;
185
- return { ...outcome, runId };
186
- }
187
- if (isPluginActivity(task.activity)) {
188
- const outcome = await stepPlugin(task, life, trace, runId);
189
- status = outcome.status;
190
- reason = outcome.reason;
191
- return { ...outcome, runId };
192
- }
193
- const nature = mind.traits.effective(mind.nature.current(now), now);
194
- const pending = task.checkpoint.pendingStep;
195
- if (pending && time.clock.remaining(task, now) > 0) {
196
- status = "engaged";
197
- reason = "接着做这一段";
198
- time.clock.release(task, task.checkpoint, now);
199
- return {
200
- status: "engaged",
201
- reason: "接着做这一段",
202
- task: task.id,
203
- runId,
204
- };
205
- }
206
- const result =
207
- pending?.result ||
208
- (await withFallback(
209
- life.chat.models,
210
- life.chat.fallbackFor(null, profile, trace),
211
- ).call(
212
- profile,
213
- "reflection",
214
- replyPrompt(
215
- nature,
216
- {
217
- ...prompts(life.repo),
218
- activity:
219
- ACTIVITY_PROMPT +
220
- '\n为本段行动估计通常需要的投入时长,输出duration:{minutes:15,basis:"按本段内容与行动估计的理由"}。只估计这一段阅读、写作或思考需要的时间,不用API处理秒数;她的实际节奏会按统一速度稍作加快。',
221
- },
222
- "activity",
223
- ),
224
- {
225
- clock: localClock(now, mind.timeZone()),
226
- activity: task.activity,
227
- affect: mind.affect.state(now),
228
- task: {
229
- id: task.id,
230
- title: task.title,
231
- why: task.why,
232
- checkpoint: task.checkpoint,
233
- },
234
- plan: {
235
- ref: task.anticipation_id
236
- ? `a:${task.anticipation_id}`
237
- : task.sources[0],
238
- content: task.title,
239
- },
240
- self: life
241
- .selfView(now, { room: task.session_id || "" })
242
- .slice(0, 8),
243
- ...(chunk ? { reading: mind.reading.passage(chunk) } : {}),
244
- creation: time.works.context(task),
245
- currentLife: time.view({ session: task.session_id, now }),
246
- },
247
- trace,
248
- ));
249
- const finished = life.now();
250
- if (
251
- task.activity === "write" &&
252
- result?.done === true &&
253
- /(?:未完待续|[((]待续[))]|尚未完成|只是开头草稿)\s*[。.!!]?\s*$/.test(
254
- String(result.content || ""),
255
- )
256
- )
257
- result.done = false;
258
- if (
259
- life.closed ||
260
- mind.nature.version() !== version ||
261
- !time.valid(task)
262
- ) {
263
- status = "cancelled";
264
- reason = "任务或服务状态已经变化";
265
- } else if (
266
- (result?.done === false && !result.content) ||
267
- result?.skip ||
268
- (task.kind === "suggestion" && result?.accepted === false)
269
- ) {
270
- reason = text(result.reason, 120) || reason;
271
- if (task.kind === "suggestion" && result?.accepted === false) {
272
- time.event(task.id, "suggestion-declined", reason, {}, finished);
273
- time.tasks.control(task.id, { action: "abandon", reason }, finished);
274
- } else
275
- time.tasks.control(
276
- task.id,
277
- {
278
- action: "pause",
279
- readyAt: finished + time.settings().breakMinutes * 60000,
280
- reason,
281
- },
282
- finished,
283
- );
284
- } else {
285
- const title = text(result?.title, 80),
286
- content = text(result?.content, 1800);
287
- if (
288
- typeof result?.done !== "boolean" ||
289
- !title ||
290
- !content ||
291
- hasCredential(title + content)
292
- )
293
- throw Error("活动成果格式无效");
294
- const sources = evidence([
295
- ...(task.anticipation_id ? [`a:${task.anticipation_id}`] : []),
296
- ...(chunk ? [`r:${chunk.id}`] : []),
297
- ...task.sources,
298
- ]);
299
- if (!time.fixtureImmediate && !pending) {
300
- this.db.exec("SAVEPOINT paced_draft");
301
- try {
302
- if (task.kind === "suggestion")
303
- time.event(
304
- task.id,
305
- "suggestion-accepted",
306
- text(result.reason, 240),
307
- {},
308
- finished,
309
- );
310
- const work = time.works.save(
311
- task,
312
- { ...result, done: false },
313
- { sources, runId, now: finished },
314
- );
315
- const checkpoint = time.tasks.get(task.id).checkpoint;
316
- time.clock.release(
317
- task,
318
- {
319
- ...checkpoint,
320
- activityClock: time.clock.plan(task, result.duration, finished),
321
- pendingStep: {
322
- result: { ...result, title, content },
323
- workId: work.id,
324
- chunk,
325
- sources,
326
- runId,
327
- },
328
- next: result.next || "接着推敲与整理这一段",
329
- },
330
- finished,
331
- );
332
- status = "draft";
333
- reason = "草稿已保存,接着完成这一段";
334
- return { status, reason, task: task.id, runId, workId: work.id };
335
- } catch (error) {
336
- this.db.exec("ROLLBACK TO paced_draft");
337
- throw error;
338
- } finally {
339
- this.db.exec("RELEASE paced_draft");
340
- }
341
- }
342
- this.db.exec("SAVEPOINT time_execution");
343
- try {
344
- if (task.kind === "suggestion" && !pending)
345
- time.event(
346
- task.id,
347
- "suggestion-accepted",
348
- text(result.reason, 240),
349
- {},
350
- finished,
351
- );
352
- if (pending) {
353
- const checkpoint = {
354
- ...task.checkpoint,
355
- activityClock: {
356
- ...task.checkpoint.activityClock,
357
- phase: "finished",
358
- },
359
- };
360
- delete checkpoint.pendingStep;
361
- this.db
362
- .prepare("UPDATE mind_time_tasks SET checkpoint=? WHERE id=?")
363
- .run(JSON.stringify(checkpoint), task.id);
364
- }
365
- const work = pending
366
- ? time.works.settle(task, pending.workId, result.done, finished)
367
- : time.works.save(task, result, {
368
- sources,
369
- runId,
370
- now: finished,
371
- });
372
- if (chunk)
373
- mind.reading.record(chunk, text(content, 120), runId, finished);
374
- if (result.done) {
375
- creation = randomUUID();
376
- this.db
377
- .prepare(
378
- "INSERT INTO mind_creations VALUES (?,?,?,?,?,?,?,?,?,?)",
379
- )
380
- .run(
381
- creation,
382
- finished,
383
- task.anticipation_id || task.id,
384
- task.activity,
385
- title,
386
- work.content,
387
- JSON.stringify(sources),
388
- task.session_id,
389
- task.discretion,
390
- runId,
391
- );
392
- mind.thoughts.add({
393
- // Narrative remains in the work. Only the act becomes an experience.
394
- kind: "reflection",
395
- content:
396
- task.activity === "write"
397
- ? `我完成了《${title}》的第 ${work.ordinal} 篇,正文保存在作品库;其中情节和人物是创作。`
398
- : text(`${title}:${content}`, 600),
399
- sources: evidence([`x:${task.id}`, ...sources]),
400
- sessions: task.session_id ? [task.session_id] : [],
401
- runId,
402
- time: finished,
403
- importance: 0.4,
404
- });
405
- time.complete(
406
- task,
407
- { creation, workId: work.id, sources },
408
- finished,
409
- );
410
- this.db
411
- .prepare(
412
- "UPDATE mind_time_works SET legacy_creation=? WHERE id=?",
413
- )
414
- .run(creation, work.id);
415
- const project = time.works.project(task.project_id);
416
- if (
417
- project.serial &&
418
- result.next &&
419
- (!result.projectState || result.projectState === "active")
420
- )
421
- time.tasks.add(
422
- {
423
- kind: "plan",
424
- activity: task.activity,
425
- title: `接续《${project.title}》第 ${work.ordinal + 1} 篇`,
426
- why: text(result.reason || result.next, 240),
427
- sources: [`x:${task.id}`],
428
- session: task.session_id,
429
- discretion: task.discretion,
430
- projectId: project.id,
431
- readyAt: finished + time.settings().breakMinutes * 60000,
432
- activeOnly: true,
433
- },
434
- finished,
435
- );
436
- if (result.share?.choice && task.session_id)
437
- time.sharing.choose(
438
- work.id,
439
- task.session_id,
440
- result.share,
441
- finished,
442
- );
443
- } else time.progress(task, work, result, finished);
444
- if (result.feeling?.feeling)
445
- mind.affect.feel({
446
- ...result.feeling,
447
- intensity: 0.2,
448
- cause: `做自己的${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "事")}时的感受`,
449
- sources: [`x:${task.id}`],
450
- session: task.session_id,
451
- origin: "activity",
452
- time: finished,
453
- });
454
- if (["paused", "complete"].includes(result.projectState))
455
- this.db
456
- .prepare("UPDATE mind_time_projects SET state=? WHERE id=?")
457
- .run(result.projectState, task.project_id);
458
- if (result.projectState === "paused" && !result.done)
459
- time.tasks.control(
460
- task.id,
461
- {
462
- action: "pause",
463
- reason: text(result.reason, 240) || "自己选择暂时放下这个项目",
464
- },
465
- finished,
466
- );
467
- this.db.exec("RELEASE time_execution");
468
- } catch (error) {
469
- this.db.exec("ROLLBACK TO time_execution");
470
- this.db.exec("RELEASE time_execution");
471
- creation = null;
472
- throw error;
473
- }
474
- status = result.done ? "written" : "draft";
475
- reason = `${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "活动")},保存了《${title}》${result.done ? "完成稿" : "草稿"}`;
476
- }
477
- } catch (error) {
478
- status = "error";
479
- reason = text(error.message, 200);
480
- trace.error = reason;
481
- if (time.valid(task))
482
- time.tasks.wait(task, reason, life.now() + 15 * 60000, life.now());
483
- } finally {
484
- if (time.valid(task))
485
- time.tasks.control(
486
- task.id,
487
- {
488
- action: "pause",
489
- readyAt: life.now() + time.settings().breakMinutes * 60000,
490
- reason,
491
- },
492
- life.now(),
493
- );
494
- trace.reason = reason;
495
- life.chat.finishQuietly(trace, status === "error" ? "error" : "complete");
496
- life.end(runId, status, reason, trace, {
497
- plan: task.anticipation_id,
498
- task: task.id,
499
- activity: task.activity,
500
- creation,
501
- });
502
- }
503
- return {
504
- status,
505
- reason,
506
- runId,
507
- activity: task.activity,
508
- creation,
509
- task: task.id,
510
- };
511
- }
512
- }
1
+ import { randomUUID } from "node:crypto";
2
+ import { localClock } from "../../core/conversation-cues.js";
3
+ import { withFallback } from "../../core/model-manager.js";
4
+ import { prompts, replyPrompt } from "../../core/persona-manager.js";
5
+ import { evidence, hasCredential, text } from "../util.js";
6
+ import { ACTIVITY_LABELS } from "./tasks.js";
7
+ import { activityLabel, isPluginActivity, needsEnergy } from "./kinds.js";
8
+ import { stepPlugin } from "./plugin-activity.js";
9
+ import { executionBlock } from "./availability.js";
10
+
11
+ const ACTIVITY_PROMPT =
12
+ '执行一个属于自己的实际活动步骤。write创作一个完整小段,已有草稿时只追加下一段,修改模式则提供修改后的正文;根据篇幅决定本篇是否结束,连载完成的是本章。read只能阅读input.reading实际提供的段落;think留下想法记录。不编造外部操作、游玩、通关或未提供的资料。虚构人物只属于作品,不能进入真实人物记忆。不输出隐藏推理。外部suggestion是别人的建议,你决定采纳或拒绝并保留理由,可以改变方向、暂停或完结项目。输出JSON {"done":false,"title":"标题","content":"最多1800字完整小段","summary":"情节摘要","next":"续接位置","bible":{"characters":"人物设定","world":"世界设定","threads":"未解决线索"},"feeling":{"feeling":"两三个字的心情","valence":0.2},"accepted":true,"reason":"自己的选择理由","projectState":"active|paused|complete","share":{"choice":"send|later|decline","reason":"分享选择"}}。完成本篇时done:true;休息/拒绝时没有content并说明reason。本步骤不发消息、不直接改变人格。';
13
+
14
+ export class TimeExecutor {
15
+ constructor(life) {
16
+ this.life = life;
17
+ this.time = life.mind.time;
18
+ this.db = life.db;
19
+ }
20
+ run(now = this.life.now()) {
21
+ return this.time.attention.activity(() => this.step(now));
22
+ }
23
+ async step(now) {
24
+ const life = this.life,
25
+ mind = life.mind,
26
+ time = this.time;
27
+ time.tasks.sync(now);
28
+ if (
29
+ life.closed ||
30
+ life.busy ||
31
+ life.repo.store.settings().demo ||
32
+ !life.settings().solitude ||
33
+ life.phase(now).key === "asleep"
34
+ )
35
+ return null;
36
+ let task, chunk;
37
+ let primary = time.primary();
38
+ if (primary && !primary.lease) {
39
+ const urgent = time.tasks
40
+ .ready(now)
41
+ .find(
42
+ (t) =>
43
+ t.priority > primary.priority &&
44
+ !executionBlock(time, t, now) &&
45
+ (time.fixtureImmediate ||
46
+ life.planner.disabledForTest ||
47
+ (t.checkpoint.schedule?.chosenAt && !t.checkpoint.reschedule)),
48
+ );
49
+ if (urgent) {
50
+ time.tasks.control(
51
+ primary.id,
52
+ {
53
+ action: "pause",
54
+ readyAt: now + 60000,
55
+ reason: "先处理一件优先级更高的事,保留续接位置",
56
+ },
57
+ now,
58
+ );
59
+ time.event(
60
+ primary.id,
61
+ "preempted",
62
+ "更高优先级事项先做",
63
+ { forTask: urgent.id, checkpoint: primary.checkpoint.next || "" },
64
+ now,
65
+ );
66
+ primary = null;
67
+ }
68
+ }
69
+ for (const candidate of primary ? [primary] : time.tasks.ready(now)) {
70
+ if (candidate.next_step > now) continue;
71
+ if (
72
+ !time.fixtureImmediate &&
73
+ !life.planner.disabledForTest &&
74
+ (!candidate.checkpoint.schedule?.chosenAt ||
75
+ candidate.checkpoint.reschedule)
76
+ )
77
+ continue;
78
+ const block = executionBlock(time, candidate, now);
79
+ if (
80
+ block &&
81
+ block !== "来源已经撤销" &&
82
+ !(candidate.activity === "read" && block === "书架没有可读资料")
83
+ ) {
84
+ time.tasks.wait(
85
+ candidate,
86
+ block,
87
+ /选定|想具体|执行能力/.test(block)
88
+ ? Number.MAX_SAFE_INTEGER
89
+ : now + 10 * 60000,
90
+ now,
91
+ );
92
+ continue;
93
+ }
94
+ if (
95
+ candidate.checkpoint.pendingStep?.chunk &&
96
+ !this.db
97
+ .prepare("SELECT 1 FROM core_chunks WHERE id=?")
98
+ .get(candidate.checkpoint.pendingStep.chunk.id)
99
+ ) {
100
+ time.tasks.wait(
101
+ candidate,
102
+ "书架这段内容已不可用",
103
+ Number.MAX_SAFE_INTEGER,
104
+ now,
105
+ );
106
+ continue;
107
+ }
108
+ if (!time.allowed(candidate, now)) {
109
+ time.tasks.control(
110
+ candidate.id,
111
+ { action: "abandon", reason: "来源已经撤销" },
112
+ now,
113
+ );
114
+ continue;
115
+ }
116
+ if (
117
+ candidate.session_id &&
118
+ !life.chat.enabled(candidate.session_id, { simulated: false })
119
+ ) {
120
+ time.tasks.wait(candidate, "来源会话目前未启用", now + 60000, now);
121
+ continue;
122
+ }
123
+ if (
124
+ !candidate.checkpoint.pendingStep &&
125
+ !mind.budget.allows("inner", now)
126
+ ) {
127
+ time.tasks.wait(candidate, "今日独处预算不足", now + 3600000, now);
128
+ continue;
129
+ }
130
+ if (
131
+ needsEnergy(candidate.activity) &&
132
+ mind.affect.state(now).energy < 0.18
133
+ ) {
134
+ time.tasks.wait(
135
+ candidate,
136
+ "精力较低,先恢复一会儿再投入",
137
+ now + 10 * 60000,
138
+ now,
139
+ );
140
+ continue;
141
+ }
142
+ if (candidate.activity === "game") {
143
+ const blocked = candidate.checkpoint.sourceIds?.length
144
+ ? ""
145
+ : time.search.ready(now);
146
+ if (blocked) {
147
+ time.tasks.wait(candidate, blocked, now + 60000, now);
148
+ continue;
149
+ }
150
+ }
151
+ chunk =
152
+ candidate.checkpoint.pendingStep?.chunk ||
153
+ (candidate.activity === "read" ? mind.reading.next(now) : null);
154
+ if (candidate.activity === "read" && !chunk) {
155
+ time.tasks.wait(candidate, "书架没有可读资料", now, now);
156
+ continue;
157
+ }
158
+ task = candidate;
159
+ break;
160
+ }
161
+ if (!task) return time.sharing.run(life, now);
162
+ let profile;
163
+ try {
164
+ if (!task.checkpoint.pendingStep) profile = life.profile();
165
+ } catch {
166
+ time.tasks.wait(task, "等待可用模型", now + 60000, now);
167
+ return null;
168
+ }
169
+ task = time.start(task, now);
170
+ if (!task) return null;
171
+ const runId = life.run(
172
+ "activity",
173
+ `${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "活动")}:${task.title}`,
174
+ ),
175
+ trace = life.repo.trace("__mind__", "activity"),
176
+ version = mind.nature.version();
177
+ let status = "empty",
178
+ reason = "这次还不想做",
179
+ creation = null;
180
+ try {
181
+ if (task.activity === "game") {
182
+ const outcome = await time.games.step(task, life, trace, runId);
183
+ status = outcome.status;
184
+ reason = outcome.reason;
185
+ return { ...outcome, runId };
186
+ }
187
+ if (isPluginActivity(task.activity)) {
188
+ const outcome = await stepPlugin(task, life, trace, runId);
189
+ status = outcome.status;
190
+ reason = outcome.reason;
191
+ return { ...outcome, runId };
192
+ }
193
+ const nature = mind.traits.effective(mind.nature.current(now), now);
194
+ const pending = task.checkpoint.pendingStep;
195
+ if (pending && time.clock.remaining(task, now) > 0) {
196
+ status = "engaged";
197
+ reason = "接着做这一段";
198
+ time.clock.release(task, task.checkpoint, now);
199
+ return {
200
+ status: "engaged",
201
+ reason: "接着做这一段",
202
+ task: task.id,
203
+ runId,
204
+ };
205
+ }
206
+ const result =
207
+ pending?.result ||
208
+ (await withFallback(
209
+ life.chat.models,
210
+ life.chat.fallbackFor(null, profile, trace),
211
+ ).call(
212
+ profile,
213
+ "reflection",
214
+ replyPrompt(
215
+ nature,
216
+ {
217
+ ...prompts(life.repo),
218
+ activity:
219
+ ACTIVITY_PROMPT +
220
+ '\n为本段行动估计通常需要的投入时长,输出duration:{minutes:15,basis:"按本段内容与行动估计的理由"}。只估计这一段阅读、写作或思考需要的时间,不用API处理秒数;她的实际节奏会按统一速度稍作加快。',
221
+ },
222
+ "activity",
223
+ ),
224
+ {
225
+ clock: localClock(now, mind.timeZone()),
226
+ activity: task.activity,
227
+ affect: mind.affect.state(now),
228
+ task: {
229
+ id: task.id,
230
+ title: task.title,
231
+ why: task.why,
232
+ checkpoint: task.checkpoint,
233
+ },
234
+ plan: {
235
+ ref: task.anticipation_id
236
+ ? `a:${task.anticipation_id}`
237
+ : task.sources[0],
238
+ content: task.title,
239
+ },
240
+ self: life
241
+ .selfView(now, { room: task.session_id || "" })
242
+ .slice(0, 8),
243
+ ...(chunk ? { reading: mind.reading.passage(chunk) } : {}),
244
+ creation: time.works.context(task),
245
+ currentLife: time.view({ session: task.session_id, now }),
246
+ },
247
+ trace,
248
+ ));
249
+ const finished = life.now();
250
+ if (
251
+ task.activity === "write" &&
252
+ result?.done === true &&
253
+ /(?:未完待续|[((]待续[))]|尚未完成|只是开头草稿)\s*[。.!!]?\s*$/.test(
254
+ String(result.content || ""),
255
+ )
256
+ )
257
+ result.done = false;
258
+ if (
259
+ life.closed ||
260
+ mind.nature.version() !== version ||
261
+ !time.valid(task)
262
+ ) {
263
+ status = "cancelled";
264
+ reason = "任务或服务状态已经变化";
265
+ } else if (
266
+ (result?.done === false && !result.content) ||
267
+ result?.skip ||
268
+ (task.kind === "suggestion" && result?.accepted === false)
269
+ ) {
270
+ reason = text(result.reason, 120) || reason;
271
+ if (task.kind === "suggestion" && result?.accepted === false) {
272
+ time.event(task.id, "suggestion-declined", reason, {}, finished);
273
+ time.tasks.control(task.id, { action: "abandon", reason }, finished);
274
+ } else
275
+ time.tasks.control(
276
+ task.id,
277
+ {
278
+ action: "pause",
279
+ readyAt: finished + time.settings().breakMinutes * 60000,
280
+ reason,
281
+ },
282
+ finished,
283
+ );
284
+ } else {
285
+ const title = text(result?.title, 80),
286
+ content = text(result?.content, 1800);
287
+ if (
288
+ typeof result?.done !== "boolean" ||
289
+ !title ||
290
+ !content ||
291
+ hasCredential(title + content)
292
+ )
293
+ throw Error("活动成果格式无效");
294
+ const sources = evidence([
295
+ ...(task.anticipation_id ? [`a:${task.anticipation_id}`] : []),
296
+ ...(chunk ? [`r:${chunk.id}`] : []),
297
+ ...task.sources,
298
+ ]);
299
+ if (!time.fixtureImmediate && !pending) {
300
+ this.db.exec("SAVEPOINT paced_draft");
301
+ try {
302
+ if (task.kind === "suggestion")
303
+ time.event(
304
+ task.id,
305
+ "suggestion-accepted",
306
+ text(result.reason, 240),
307
+ {},
308
+ finished,
309
+ );
310
+ const work = time.works.save(
311
+ task,
312
+ { ...result, done: false },
313
+ { sources, runId, now: finished },
314
+ );
315
+ const checkpoint = time.tasks.get(task.id).checkpoint;
316
+ time.clock.release(
317
+ task,
318
+ {
319
+ ...checkpoint,
320
+ activityClock: time.clock.plan(task, result.duration, finished),
321
+ pendingStep: {
322
+ result: { ...result, title, content },
323
+ workId: work.id,
324
+ chunk,
325
+ sources,
326
+ runId,
327
+ },
328
+ next: result.next || "接着推敲与整理这一段",
329
+ },
330
+ finished,
331
+ );
332
+ status = "draft";
333
+ reason = "草稿已保存,接着完成这一段";
334
+ return { status, reason, task: task.id, runId, workId: work.id };
335
+ } catch (error) {
336
+ this.db.exec("ROLLBACK TO paced_draft");
337
+ throw error;
338
+ } finally {
339
+ this.db.exec("RELEASE paced_draft");
340
+ }
341
+ }
342
+ this.db.exec("SAVEPOINT time_execution");
343
+ try {
344
+ if (task.kind === "suggestion" && !pending)
345
+ time.event(
346
+ task.id,
347
+ "suggestion-accepted",
348
+ text(result.reason, 240),
349
+ {},
350
+ finished,
351
+ );
352
+ if (pending) {
353
+ const checkpoint = {
354
+ ...task.checkpoint,
355
+ activityClock: {
356
+ ...task.checkpoint.activityClock,
357
+ phase: "finished",
358
+ },
359
+ };
360
+ delete checkpoint.pendingStep;
361
+ this.db
362
+ .prepare("UPDATE mind_time_tasks SET checkpoint=? WHERE id=?")
363
+ .run(JSON.stringify(checkpoint), task.id);
364
+ }
365
+ const work = pending
366
+ ? time.works.settle(task, pending.workId, result.done, finished)
367
+ : time.works.save(task, result, {
368
+ sources,
369
+ runId,
370
+ now: finished,
371
+ });
372
+ if (chunk)
373
+ mind.reading.record(chunk, text(content, 120), runId, finished);
374
+ if (result.done) {
375
+ creation = randomUUID();
376
+ this.db
377
+ .prepare(
378
+ "INSERT INTO mind_creations VALUES (?,?,?,?,?,?,?,?,?,?)",
379
+ )
380
+ .run(
381
+ creation,
382
+ finished,
383
+ task.anticipation_id || task.id,
384
+ task.activity,
385
+ title,
386
+ work.content,
387
+ JSON.stringify(sources),
388
+ task.session_id,
389
+ task.discretion,
390
+ runId,
391
+ );
392
+ mind.thoughts.add({
393
+ // Narrative remains in the work. Only the act becomes an experience.
394
+ kind: "reflection",
395
+ content:
396
+ task.activity === "write"
397
+ ? `我完成了《${title}》的第 ${work.ordinal} 篇,正文保存在作品库;其中情节和人物是创作。`
398
+ : text(`${title}:${content}`, 600),
399
+ sources: evidence([`x:${task.id}`, ...sources]),
400
+ sessions: task.session_id ? [task.session_id] : [],
401
+ runId,
402
+ time: finished,
403
+ importance: 0.4,
404
+ });
405
+ time.complete(
406
+ task,
407
+ { creation, workId: work.id, sources },
408
+ finished,
409
+ );
410
+ this.db
411
+ .prepare(
412
+ "UPDATE mind_time_works SET legacy_creation=? WHERE id=?",
413
+ )
414
+ .run(creation, work.id);
415
+ const project = time.works.project(task.project_id);
416
+ if (
417
+ project.serial &&
418
+ result.next &&
419
+ (!result.projectState || result.projectState === "active")
420
+ )
421
+ time.tasks.add(
422
+ {
423
+ kind: "plan",
424
+ activity: task.activity,
425
+ title: `接续《${project.title}》第 ${work.ordinal + 1} 篇`,
426
+ why: text(result.reason || result.next, 240),
427
+ sources: [`x:${task.id}`],
428
+ session: task.session_id,
429
+ discretion: task.discretion,
430
+ projectId: project.id,
431
+ readyAt: finished + time.settings().breakMinutes * 60000,
432
+ activeOnly: true,
433
+ },
434
+ finished,
435
+ );
436
+ if (result.share?.choice && task.session_id)
437
+ time.sharing.choose(
438
+ work.id,
439
+ task.session_id,
440
+ result.share,
441
+ finished,
442
+ );
443
+ } else time.progress(task, work, result, finished);
444
+ if (result.feeling?.feeling)
445
+ mind.affect.feel({
446
+ ...result.feeling,
447
+ intensity: 0.2,
448
+ cause: `做自己的${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "事")}时的感受`,
449
+ sources: [`x:${task.id}`],
450
+ session: task.session_id,
451
+ origin: "activity",
452
+ time: finished,
453
+ });
454
+ if (["paused", "complete"].includes(result.projectState))
455
+ this.db
456
+ .prepare("UPDATE mind_time_projects SET state=? WHERE id=?")
457
+ .run(result.projectState, task.project_id);
458
+ if (result.projectState === "paused" && !result.done)
459
+ time.tasks.control(
460
+ task.id,
461
+ {
462
+ action: "pause",
463
+ reason: text(result.reason, 240) || "自己选择暂时放下这个项目",
464
+ },
465
+ finished,
466
+ );
467
+ this.db.exec("RELEASE time_execution");
468
+ } catch (error) {
469
+ this.db.exec("ROLLBACK TO time_execution");
470
+ this.db.exec("RELEASE time_execution");
471
+ creation = null;
472
+ throw error;
473
+ }
474
+ status = result.done ? "written" : "draft";
475
+ reason = `${activityLabel(task.activity, ACTIVITY_LABELS[task.activity] || "活动")},保存了《${title}》${result.done ? "完成稿" : "草稿"}`;
476
+ }
477
+ } catch (error) {
478
+ status = "error";
479
+ reason = text(error.message, 200);
480
+ trace.error = reason;
481
+ if (time.valid(task))
482
+ time.tasks.wait(task, reason, life.now() + 15 * 60000, life.now());
483
+ } finally {
484
+ if (time.valid(task))
485
+ time.tasks.control(
486
+ task.id,
487
+ {
488
+ action: "pause",
489
+ readyAt: life.now() + time.settings().breakMinutes * 60000,
490
+ reason,
491
+ },
492
+ life.now(),
493
+ );
494
+ trace.reason = reason;
495
+ life.chat.finishQuietly(trace, status === "error" ? "error" : "complete");
496
+ life.end(runId, status, reason, trace, {
497
+ plan: task.anticipation_id,
498
+ task: task.id,
499
+ activity: task.activity,
500
+ creation,
501
+ });
502
+ }
503
+ return {
504
+ status,
505
+ reason,
506
+ runId,
507
+ activity: task.activity,
508
+ creation,
509
+ task: task.id,
510
+ };
511
+ }
512
+ }