luckytri 1.0.7 → 1.0.9
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/README.en.md +7 -3
- package/README.md +7 -3
- package/docs/en/CHANGELOG.md +45 -24
- package/docs/en/guide.md +3 -1
- package/docs/zh/CHANGELOG.md +45 -24
- package/docs/zh/guide.md +3 -1
- package/package.json +1 -1
- package/public/guide.en.html +13 -5
- package/public/guide.html +12 -5
- package/public/tutorial.en.md +3 -1
- package/public/tutorial.md +3 -1
- package/server/core/context-builder.js +22 -3
- package/server/core/contribution-check.js +50 -0
- package/server/core/conversation-cues.js +29 -17
- package/server/core/conversation-grounding.js +377 -8
- package/server/core/dialogue-context.js +197 -2
- package/server/core/dialogue-contract.js +59 -0
- package/server/core/initiative-context.js +40 -18
- package/server/core/model-json.js +36 -3
- package/server/core/model-manager.js +7 -10
- package/server/core/model-presets.js +169 -24
- package/server/core/persona-manager.js +52 -12
- package/server/core/reader-check.js +166 -0
- package/server/core/reply-delivery.js +216 -10
- package/server/core/repository.js +33 -0
- package/server/core/response-generator.js +53 -10
- package/server/core/response-validator.js +42 -9
- package/server/core/turn-processor.js +26 -4
- package/server/core/turn.js +298 -51
- package/server/i18n/decisions.js +16 -0
- package/server/i18n/errors.js +4 -0
- package/server/i18n/labels.js +6 -5
- package/server/i18n/models.js +11 -2
- package/server/mind/expression-grounding.js +24 -0
- package/server/mind/own-voice.js +35 -0
- package/server/mind/reading.js +15 -3
- package/server/mind/time/activity-recall.js +65 -0
- package/server/mind/time/availability.js +23 -1
- package/server/mind/time/delivery-links.js +130 -130
- package/server/mind/time/executor.js +4 -2
- package/server/mind/time/games.js +13 -8
- package/server/mind/time/index.js +11 -3
- package/server/mind/time/kinds.js +29 -2
- package/server/mind/time/material-relevance.js +19 -0
- package/server/mind/time/reading-input.js +31 -0
- package/server/mind/time/sharing.js +10 -0
- package/server/mind/time/tasks.js +39 -3
- package/server/mind/time/works.js +105 -15
- package/server/mind/view.js +29 -9
|
@@ -8,6 +8,7 @@ import { deadlineAt, taskSchedule, spokenDate } from "./schedule.js";
|
|
|
8
8
|
import { DeliveryLinks } from "./delivery-links.js";
|
|
9
9
|
import { activityPresentation } from "./presentation.js";
|
|
10
10
|
import { activityLabel, classifyActivity, isExecutable } from "./kinds.js";
|
|
11
|
+
import { awaitsTextInput, nextReadingChunk } from "./reading-input.js";
|
|
11
12
|
|
|
12
13
|
export const TASK_STATES = [
|
|
13
14
|
"todo",
|
|
@@ -105,10 +106,13 @@ export class Tasks {
|
|
|
105
106
|
const privateRoots = this.time.mind.meetings.privateRoots(refs, now);
|
|
106
107
|
session = privateRoots[0] || session;
|
|
107
108
|
if (privateRoots.length && discretion !== "secret") discretion = "private";
|
|
109
|
+
const inferred = classifyActivity(title);
|
|
108
110
|
const type =
|
|
109
|
-
|
|
110
|
-
?
|
|
111
|
-
:
|
|
111
|
+
awaitsTextInput(title) && inferred === "read"
|
|
112
|
+
? "read"
|
|
113
|
+
: activity && (ACTIVITY_LABELS[activity] || isExecutable(activity))
|
|
114
|
+
? activity
|
|
115
|
+
: inferred;
|
|
112
116
|
subject =
|
|
113
117
|
intentRecipient(this.db, { session, subject, title, sources: refs }) ||
|
|
114
118
|
null;
|
|
@@ -207,6 +211,22 @@ export class Tasks {
|
|
|
207
211
|
{ sources: refs },
|
|
208
212
|
now,
|
|
209
213
|
);
|
|
214
|
+
if (type === "read" && awaitsTextInput(title)) {
|
|
215
|
+
this.db.prepare("UPDATE mind_time_tasks SET checkpoint=? WHERE id=?").run(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
awaitedText: true,
|
|
218
|
+
expectedTextTitle: intent.topic,
|
|
219
|
+
awaitedAfter: now,
|
|
220
|
+
}),
|
|
221
|
+
id,
|
|
222
|
+
);
|
|
223
|
+
this.wait(
|
|
224
|
+
this.get(id),
|
|
225
|
+
"等待约定的正文或资料",
|
|
226
|
+
Number.MAX_SAFE_INTEGER,
|
|
227
|
+
now,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
210
230
|
return { id };
|
|
211
231
|
}
|
|
212
232
|
sync(now = this.time.now()) {
|
|
@@ -326,6 +346,22 @@ export class Tasks {
|
|
|
326
346
|
return ids;
|
|
327
347
|
}
|
|
328
348
|
ready(now = this.time.now()) {
|
|
349
|
+
for (const row of this.db
|
|
350
|
+
.prepare(
|
|
351
|
+
"SELECT * FROM mind_time_tasks WHERE state='waiting' AND activity='read' AND json_extract(checkpoint,'$.awaitedText')=1 AND json_extract(checkpoint,'$.mergedInto') IS NULL",
|
|
352
|
+
)
|
|
353
|
+
.all()) {
|
|
354
|
+
const task = decode(row);
|
|
355
|
+
if (
|
|
356
|
+
this.time.allowed(task, now) &&
|
|
357
|
+
nextReadingChunk(this.time.mind, task, now)
|
|
358
|
+
)
|
|
359
|
+
this.control(
|
|
360
|
+
task.id,
|
|
361
|
+
{ action: "resume", reason: "约定的正文已可阅读" },
|
|
362
|
+
now,
|
|
363
|
+
);
|
|
364
|
+
}
|
|
329
365
|
return this.db
|
|
330
366
|
.prepare(
|
|
331
367
|
"SELECT * FROM mind_time_tasks WHERE state IN ('todo','scheduled','waiting','paused') AND ready_at<=? AND next_step<=? AND activity!='unknown' AND json_extract(checkpoint,'$.mergedInto') IS NULL ORDER BY priority DESC,created LIMIT 100",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { evidence, hasCredential, parse, text } from "../util.js";
|
|
3
3
|
import { activityPresentation, gameText } from "./presentation.js";
|
|
4
|
+
import { interestTerms } from "../attention.js";
|
|
5
|
+
import { belongsToGame } from "./material-relevance.js";
|
|
6
|
+
import { gameTopic } from "./intent.js";
|
|
4
7
|
|
|
5
8
|
const decode = (row) =>
|
|
6
9
|
row
|
|
@@ -145,7 +148,7 @@ export class Works {
|
|
|
145
148
|
: row,
|
|
146
149
|
);
|
|
147
150
|
}
|
|
148
|
-
get(id, version) {
|
|
151
|
+
get(id, version, { before = Number.MAX_SAFE_INTEGER } = {}) {
|
|
149
152
|
const work = this.db
|
|
150
153
|
.prepare("SELECT * FROM mind_time_works WHERE id=?")
|
|
151
154
|
.get(id);
|
|
@@ -156,9 +159,9 @@ export class Works {
|
|
|
156
159
|
const saved = v
|
|
157
160
|
? this.db
|
|
158
161
|
.prepare(
|
|
159
|
-
"SELECT reason FROM mind_time_events WHERE kind='draft' AND json_extract(data,'$.workId')=? AND json_extract(data,'$.version')=? ORDER BY id DESC LIMIT 1",
|
|
162
|
+
"SELECT reason FROM mind_time_events WHERE kind='draft' AND json_extract(data,'$.workId')=? AND json_extract(data,'$.version')=? AND created<=? ORDER BY id DESC LIMIT 1",
|
|
160
163
|
)
|
|
161
|
-
.get(id, v.version)
|
|
164
|
+
.get(id, v.version, before)
|
|
162
165
|
: null;
|
|
163
166
|
return v
|
|
164
167
|
? {
|
|
@@ -170,7 +173,9 @@ export class Works {
|
|
|
170
173
|
? saved.reason === "保存完成稿"
|
|
171
174
|
? "complete"
|
|
172
175
|
: "draft"
|
|
173
|
-
: work.
|
|
176
|
+
: work.updated > before
|
|
177
|
+
? "draft"
|
|
178
|
+
: work.state,
|
|
174
179
|
sources: parse(v.sources, []),
|
|
175
180
|
provenance: parse(v.provenance, {}),
|
|
176
181
|
versions: this.db
|
|
@@ -396,9 +401,19 @@ export class Works {
|
|
|
396
401
|
}
|
|
397
402
|
fragments({ session, now = this.time.now(), cue = [] } = {}) {
|
|
398
403
|
const words = (Array.isArray(cue) ? cue : [cue])
|
|
399
|
-
.map((v) => (typeof v === "string" ? v :
|
|
404
|
+
.map((v) => (typeof v === "string" ? v : v?.text || ""))
|
|
400
405
|
.join(" ");
|
|
401
|
-
|
|
406
|
+
const terms = interestTerms([words]);
|
|
407
|
+
// Search the work catalogue, not just the two newest activities. Select
|
|
408
|
+
// the version that existed at the requested time, including in replays.
|
|
409
|
+
return this.db
|
|
410
|
+
.prepare(
|
|
411
|
+
`SELECT w.*,COALESCE(p.kind,(SELECT kind FROM mind_creations WHERE id=w.legacy_creation)) kind,p.title project_title,p.bible,v.version,v.title,v.created version_created,v.summary,v.sources,v.provenance,length(v.content) characters
|
|
412
|
+
FROM mind_time_works w LEFT JOIN mind_time_projects p ON p.id=w.project_id
|
|
413
|
+
JOIN mind_time_versions v ON v.work_id=w.id AND v.version=(SELECT max(h.version) FROM mind_time_versions h WHERE h.work_id=w.id AND h.created<=?)
|
|
414
|
+
WHERE w.created<=? ORDER BY v.created DESC`,
|
|
415
|
+
)
|
|
416
|
+
.all(now, now)
|
|
402
417
|
.filter(
|
|
403
418
|
(w) =>
|
|
404
419
|
w.created <= now &&
|
|
@@ -410,29 +425,104 @@ export class Works {
|
|
|
410
425
|
)
|
|
411
426
|
.map((w) => ({
|
|
412
427
|
...w,
|
|
413
|
-
score: [...
|
|
414
|
-
(term) =>
|
|
415
|
-
|
|
416
|
-
|
|
428
|
+
score: [...interestTerms([w.title, w.project_title, w.summary])]
|
|
429
|
+
.filter((term) => terms.has(term))
|
|
430
|
+
.reduce(
|
|
431
|
+
(total, term) => total + (/^[a-z][a-z0-9]{2,}$/.test(term) ? 8 : 1),
|
|
432
|
+
0,
|
|
433
|
+
),
|
|
417
434
|
}))
|
|
418
|
-
.sort(
|
|
419
|
-
|
|
435
|
+
.sort(
|
|
436
|
+
(a, b) => b.score - a.score || b.version_created - a.version_created,
|
|
437
|
+
)
|
|
438
|
+
.filter((w, index, rows) =>
|
|
439
|
+
rows[0]?.score >= 2 ? w.score >= 2 : index < 2,
|
|
440
|
+
)
|
|
441
|
+
.filter((w) => this.materialSupported(w))
|
|
442
|
+
.slice(0, 3)
|
|
420
443
|
.map((w) => ({
|
|
421
444
|
id: w.id,
|
|
422
445
|
title: w.title,
|
|
423
|
-
state: w.state,
|
|
446
|
+
state: this.get(w.id, w.version, { before: now }).state,
|
|
424
447
|
kind: w.kind,
|
|
425
448
|
version: w.version,
|
|
426
449
|
ordinal: w.ordinal,
|
|
427
450
|
summary: text(w.summary, 180),
|
|
428
|
-
fragment: text(this.get(w.id).content,
|
|
451
|
+
fragment: text(this.get(w.id, w.version).content, 300),
|
|
429
452
|
...(w.kind === "game"
|
|
430
453
|
? {
|
|
431
454
|
experienceMode: "reference",
|
|
432
|
-
provenance: this.get(w.id).provenance,
|
|
455
|
+
provenance: this.get(w.id, w.version).provenance,
|
|
433
456
|
}
|
|
434
457
|
: {}),
|
|
435
458
|
characters: w.characters,
|
|
436
459
|
}));
|
|
437
460
|
}
|
|
461
|
+
|
|
462
|
+
activityEvidence({ session, now = this.time.now(), cue = [] } = {}) {
|
|
463
|
+
const terms = interestTerms(
|
|
464
|
+
(Array.isArray(cue) ? cue : [cue]).map((v) =>
|
|
465
|
+
typeof v === "string"
|
|
466
|
+
? v
|
|
467
|
+
: v?.role === "assistant" && !v.activityEvidence
|
|
468
|
+
? ""
|
|
469
|
+
: v?.text || "",
|
|
470
|
+
),
|
|
471
|
+
);
|
|
472
|
+
if (!terms.size) return [];
|
|
473
|
+
// A private conversation can motivate her own reading. Its contents and
|
|
474
|
+
// promises stay private; absence from the public fragments must not erase
|
|
475
|
+
// the fact that the reading took place. Search may also use a same-speaker
|
|
476
|
+
// reply delivered in this room to resolve an implicit follow-up.
|
|
477
|
+
return this.db
|
|
478
|
+
.prepare(
|
|
479
|
+
`SELECT w.id,w.title,w.discretion,w.session_id,p.kind,p.title project_title,p.bible,v.created,v.provenance,v.sources
|
|
480
|
+
FROM mind_time_works w JOIN mind_time_projects p ON p.id=w.project_id
|
|
481
|
+
JOIN mind_time_versions v ON v.work_id=w.id AND v.version=(SELECT max(h.version) FROM mind_time_versions h WHERE h.work_id=w.id AND h.created<=?)
|
|
482
|
+
WHERE w.created<=? AND p.kind='game' AND w.discretion!='secret' ORDER BY v.created DESC`,
|
|
483
|
+
)
|
|
484
|
+
.all(now, now)
|
|
485
|
+
.filter((w) => {
|
|
486
|
+
const shared = [...interestTerms([w.title, w.project_title])].filter(
|
|
487
|
+
(term) => terms.has(term),
|
|
488
|
+
);
|
|
489
|
+
return (
|
|
490
|
+
(shared.length >= 2 ||
|
|
491
|
+
shared.some((term) => /^[a-z][a-z0-9]{2,}$/.test(term))) &&
|
|
492
|
+
this.time.allowed({ ...w, sources: parse(w.sources, []) }, now) &&
|
|
493
|
+
this.materialSupported(w)
|
|
494
|
+
);
|
|
495
|
+
})
|
|
496
|
+
.slice(0, 3)
|
|
497
|
+
.map((w) => ({
|
|
498
|
+
title: w.title,
|
|
499
|
+
activity: "阅读游戏资料并写笔记",
|
|
500
|
+
experienceMode: "reference",
|
|
501
|
+
...(w.session_id !== session && w.discretion !== "open"
|
|
502
|
+
? { privateOrigin: true }
|
|
503
|
+
: {}),
|
|
504
|
+
instruction:
|
|
505
|
+
"仅用于核对自己确有这项阅读和笔记,不能据此否认从未发生;这里不提供私下正文、发起人、约定或对话。只能自然回应本轮对方已经提到的阅读本身,不披露私下内容。",
|
|
506
|
+
}));
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
materialSupported(work) {
|
|
510
|
+
const project = work.kind ? null : this.project(work.project_id);
|
|
511
|
+
if ((work.kind || project?.kind) !== "game") return true;
|
|
512
|
+
const ids = parse(work.provenance, {}).sourceIds;
|
|
513
|
+
const topic =
|
|
514
|
+
parse(work.bible, {}).topic ||
|
|
515
|
+
project?.bible?.topic ||
|
|
516
|
+
gameTopic(work.project_title || project?.title);
|
|
517
|
+
// Missing old provenance is unknown, not evidence that reading never took
|
|
518
|
+
// place. Known references all belonging to another subject are different.
|
|
519
|
+
if (!Array.isArray(ids) || !ids.length || !topic) return true;
|
|
520
|
+
const source = this.db.prepare(
|
|
521
|
+
"SELECT title,url,content FROM mind_time_sources WHERE id=?",
|
|
522
|
+
);
|
|
523
|
+
return ids.some((id) => {
|
|
524
|
+
const row = source.get(id);
|
|
525
|
+
return row && belongsToGame(row, topic);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
438
528
|
}
|
package/server/mind/view.js
CHANGED
|
@@ -6,7 +6,10 @@ import { describeFace } from "./faces.js";
|
|
|
6
6
|
import { SELF_KINDS } from "./self.js";
|
|
7
7
|
import { DAY, text } from "./util.js";
|
|
8
8
|
import { relationshipHistory } from "./relationship-context.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
fixedReplyHabit,
|
|
11
|
+
conversationalMemory,
|
|
12
|
+
} from "../core/conversation-grounding.js";
|
|
10
13
|
|
|
11
14
|
const FORGOTTEN = "(很久没想起了)";
|
|
12
15
|
|
|
@@ -92,7 +95,14 @@ function feedback(mind, session, now) {
|
|
|
92
95
|
// so anything this batch of messages brings back to mind belongs there.
|
|
93
96
|
export function innerView(
|
|
94
97
|
mind,
|
|
95
|
-
{
|
|
98
|
+
{
|
|
99
|
+
session,
|
|
100
|
+
kind = "group",
|
|
101
|
+
people = [],
|
|
102
|
+
cue = [],
|
|
103
|
+
activityCue = cue,
|
|
104
|
+
now = Date.now(),
|
|
105
|
+
},
|
|
96
106
|
) {
|
|
97
107
|
const nature = mind.nature.current(now);
|
|
98
108
|
const affect = mind.affect.state(now, { nature, room: session });
|
|
@@ -172,9 +182,14 @@ export function innerView(
|
|
|
172
182
|
? {
|
|
173
183
|
here: describeFace({
|
|
174
184
|
...face,
|
|
175
|
-
notes: face.notes
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
notes: face.notes
|
|
186
|
+
.map((note) => ({
|
|
187
|
+
...note,
|
|
188
|
+
content: conversationalMemory(note.content),
|
|
189
|
+
}))
|
|
190
|
+
.filter(
|
|
191
|
+
(note) => note.content && spoken(`${note.content} ${note.why}`),
|
|
192
|
+
),
|
|
178
193
|
role: spoken(face.role),
|
|
179
194
|
tone: spoken(face.tone),
|
|
180
195
|
aspiration: spoken(face.aspiration),
|
|
@@ -240,7 +255,11 @@ export function innerView(
|
|
|
240
255
|
const thoughts = relevantContext(
|
|
241
256
|
mind.thoughts
|
|
242
257
|
.open({ now, limit: 400, session })
|
|
243
|
-
.filter(
|
|
258
|
+
.filter(
|
|
259
|
+
(t) =>
|
|
260
|
+
!fixedReplyHabit(t.content) &&
|
|
261
|
+
mind.meetings.sayable(t.content, session),
|
|
262
|
+
),
|
|
244
263
|
{ cues, limit: 3, requireOverlap: cue.length > 0 },
|
|
245
264
|
);
|
|
246
265
|
const heard = feedback(mind, session, now);
|
|
@@ -257,6 +276,7 @@ export function innerView(
|
|
|
257
276
|
});
|
|
258
277
|
const wills = mind.meetings.traces({ session, before: now, limit: 2 });
|
|
259
278
|
const will = wills
|
|
279
|
+
.filter((row) => !fixedReplyHabit(row.text))
|
|
260
280
|
.map((row) =>
|
|
261
281
|
row.living ? row.text : `${text(row.content, 28)}:${row.text}`,
|
|
262
282
|
)
|
|
@@ -272,8 +292,8 @@ export function innerView(
|
|
|
272
292
|
affect.cause && mind.meetings.sayable(affect.cause, session)
|
|
273
293
|
? text(affect.cause, 40)
|
|
274
294
|
: "";
|
|
275
|
-
const withLines = withWhom.filter(
|
|
276
|
-
mind.meetings.sayable(line, session),
|
|
295
|
+
const withLines = withWhom.filter(
|
|
296
|
+
(line) => !fixedReplyHabit(line) && mind.meetings.sayable(line, session),
|
|
277
297
|
);
|
|
278
298
|
const expectingLines = expecting.filter((line) =>
|
|
279
299
|
mind.meetings.sayable(line, session),
|
|
@@ -287,7 +307,7 @@ export function innerView(
|
|
|
287
307
|
self,
|
|
288
308
|
affect: cause ? affect : { ...affect, cause: "" },
|
|
289
309
|
inner: {
|
|
290
|
-
currentLife: mind.time.view({ session, now, cue }),
|
|
310
|
+
currentLife: mind.time.view({ session, now, cue, activityCue }),
|
|
291
311
|
state: `${affect.phaseLabel},精力${affect.energyLabel},心情${affect.mood}${cause ? `(${cause})` : ""}${affect.lately ? `,${affect.lately}` : ""}`,
|
|
292
312
|
...(persons.length ? { people: persons } : {}),
|
|
293
313
|
...(relationships.known.length ? { relationships } : {}),
|