luckytri 1.0.3 → 1.0.5
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/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +14 -0
- package/docs/en/guide.md +5 -1
- package/docs/en/webui.md +3 -1
- package/docs/zh/CHANGELOG.md +14 -0
- package/docs/zh/guide.md +5 -1
- package/docs/zh/webui.md +3 -1
- package/package.json +4 -3
- package/public/app/assets/{ChatsPage-B83Evc79.js → ChatsPage-CshdtTzV.js} +1 -1
- package/public/app/assets/{Empty-ddEfu0w8.js → Empty-C7yzOBzy.js} +1 -1
- package/public/app/assets/{Field-mIhV5YBA.js → Field-BjFv4Rfn.js} +1 -1
- package/public/app/assets/{HeartPage-BGEn24rc.js → HeartPage-eqo6ru16.js} +1 -1
- package/public/app/assets/{LifePage-BleqPj4O.js → LifePage-CN8GE5NK.js} +1 -1
- package/public/app/assets/{MemoryPage-BM3-0_lF.js → MemoryPage-CCpMN4Wg.js} +1 -1
- package/public/app/assets/{NaturePage-CxxMnQIc.js → NaturePage-C0JqdBTR.js} +2 -2
- package/public/app/assets/{PeoplePage-DDDHESQp.css → PeoplePage-BTU0H1pM.css} +1 -1
- package/public/app/assets/PeoplePage-BXGBbO4u.js +1 -0
- package/public/app/assets/{PluginsPage-DE_-Q3ra.js → PluginsPage-Dksp5AV5.js} +1 -1
- package/public/app/assets/{SystemPage-Be1a6UDP.css → SystemPage-BenIyTvk.css} +1 -1
- package/public/app/assets/{SystemPage-BWQKEQRU.js → SystemPage-Cd7aa8eE.js} +1 -1
- package/public/app/assets/{TimePage-Dzt8t8kL.js → TimePage-Bs_zImb4.js} +1 -1
- package/public/app/assets/{index-DY4e15Kh.css → index-BHZcHUMh.css} +1 -1
- package/public/app/assets/{index-oLIXWxsn.js → index-CyRtYxP3.js} +3 -3
- package/public/app/assets/{knowledge-Dvtmpd45.js → knowledge-BJOIyuJo.js} +1 -1
- package/public/app/assets/{media-keE52cIX.js → media-Bwtx8blz.js} +1 -1
- package/public/app/assets/mind-DtTVrW6Z.js +1 -0
- package/public/app/index.html +4 -4
- package/public/guide.en.html +31 -8
- package/public/guide.html +14 -5
- package/public/tutorial.en.md +5 -1
- package/public/tutorial.md +5 -1
- package/scripts/launch.js +11 -4
- package/scripts/service.js +57 -7
- package/scripts/start-server.js +4 -1
- package/scripts/update-instance.js +201 -0
- package/server/app.js +1 -1
- package/server/core/context-builder.js +2 -2
- package/server/core/context-compactor.js +2 -2
- package/server/core/orchestrator.js +1 -0
- package/server/core/persona-manager.js +11 -0
- package/server/core/reply-delivery.js +11 -0
- package/server/core/response-validator.js +33 -1
- package/server/i18n/decisions.js +4 -1
- package/server/i18n/errors.js +29 -0
- package/server/index.js +45 -2
- package/server/mind/api.js +25 -0
- package/server/mind/bonds.js +9 -2
- package/server/mind/index.js +2 -0
- package/server/mind/initiative.js +6 -1
- package/server/mind/life/context.js +22 -4
- package/server/mind/life/diary.js +20 -0
- package/server/mind/life/growth.js +13 -5
- package/server/mind/life/index.js +10 -2
- package/server/mind/nature.js +23 -2
- package/server/mind/relationship-context.js +71 -0
- package/server/mind/relationships.js +390 -0
- package/server/mind/schema.js +2 -0
- package/server/mind/time/tasks.js +2 -1
- package/server/mind/view.js +11 -1
- package/server/runtime/installation.js +116 -0
- package/server/runtime/update-execution.js +127 -0
- package/server/runtime/update-manager.js +140 -0
- package/server/startup-lock.js +12 -12
- package/server/storage/archive.js +10 -2
- package/server/storage/backup-lock.js +23 -5
- package/server/storage/data-transfer.js +2 -1
- package/server/studio/display-names.js +1 -0
- package/server/studio/settings.js +1 -0
- package/server/studio/updates.js +14 -3
- package/public/app/assets/PeoplePage-D12cvuy7.js +0 -1
- package/public/app/assets/mind-DYRfFMwh.js +0 -1
package/server/mind/api.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { wrap } from "../http.js";
|
|
2
2
|
import { mountTime } from "./time/api.js";
|
|
3
|
+
import { RELATIONSHIP_ROLES } from "./relationships.js";
|
|
3
4
|
import { localClock } from "../core/conversation-cues.js";
|
|
4
5
|
import { agoLabel, elapsedLabel, JUST_NOW } from "./clock.js";
|
|
5
6
|
import { diffSnapshots } from "./index.js";
|
|
@@ -77,6 +78,29 @@ function activityOf({ db, life, now, affect, words, names }) {
|
|
|
77
78
|
export function mountMind(app, chat, life) {
|
|
78
79
|
const { mind } = chat;
|
|
79
80
|
mountTime(app, life);
|
|
81
|
+
app.get(
|
|
82
|
+
"/api/mind/relationships",
|
|
83
|
+
wrap((_req, res) =>
|
|
84
|
+
res.json({
|
|
85
|
+
roles: RELATIONSHIP_ROLES,
|
|
86
|
+
items: mind.relationships
|
|
87
|
+
.list(life.now())
|
|
88
|
+
.map((r) => mind.relationships.view(r)),
|
|
89
|
+
}),
|
|
90
|
+
),
|
|
91
|
+
);
|
|
92
|
+
app.put(
|
|
93
|
+
"/api/mind/relationships",
|
|
94
|
+
wrap((req, res) => res.json(mind.relationships.save(req.body, life.now()))),
|
|
95
|
+
);
|
|
96
|
+
app.delete(
|
|
97
|
+
"/api/mind/relationships/:id",
|
|
98
|
+
wrap((req, res) =>
|
|
99
|
+
res.json(
|
|
100
|
+
mind.relationships.end(String(req.params.id), req.body, life.now()),
|
|
101
|
+
),
|
|
102
|
+
),
|
|
103
|
+
);
|
|
80
104
|
// A small, cheap picture of TA for the studio: asked for on every change.
|
|
81
105
|
app.get(
|
|
82
106
|
"/api/mind/presence",
|
|
@@ -287,6 +311,7 @@ export function mountMind(app, chat, life) {
|
|
|
287
311
|
})),
|
|
288
312
|
},
|
|
289
313
|
changes: mind.bonds.changes("person", id, 60),
|
|
314
|
+
relationshipHistory: mind.relationships.history(id),
|
|
290
315
|
memories: db
|
|
291
316
|
.prepare(
|
|
292
317
|
"SELECT id,session_id,content,type,confidence,importance,status,discretion,locked,created,superseded_by FROM core_memories WHERE subject=? AND status IN ('confirmed','superseded') ORDER BY CASE status WHEN 'confirmed' THEN 0 ELSE 1 END, created DESC LIMIT 80",
|
package/server/mind/bonds.js
CHANGED
|
@@ -235,7 +235,7 @@ export class Bonds {
|
|
|
235
235
|
"SELECT sessions FROM mind_people WHERE user_id=?",
|
|
236
236
|
);
|
|
237
237
|
const write = this.db.prepare(
|
|
238
|
-
"INSERT INTO mind_people(user_id,name,first_seen,last_seen,sessions) VALUES (?,?,?,?,?) ON CONFLICT(user_id) DO UPDATE SET name=COALESCE(excluded.name,name),last_seen=MAX(last_seen,excluded.last_seen),sessions=excluded.sessions",
|
|
238
|
+
"INSERT INTO mind_people(user_id,name,first_seen,last_seen,sessions) VALUES (?,?,?,?,?) ON CONFLICT(user_id) DO UPDATE SET name=COALESCE(excluded.name,name),first_seen=COALESCE(first_seen,excluded.first_seen),last_seen=MAX(COALESCE(last_seen,excluded.last_seen),excluded.last_seen),sessions=excluded.sessions",
|
|
239
239
|
);
|
|
240
240
|
for (const { userId, name } of people) {
|
|
241
241
|
if (!userId || userId === "bot") continue;
|
|
@@ -450,6 +450,9 @@ export class Bonds {
|
|
|
450
450
|
);
|
|
451
451
|
const merged = {
|
|
452
452
|
...(state || { ...START, interactions: 0 }),
|
|
453
|
+
firstMetAt:
|
|
454
|
+
state?.firstMetAt ??
|
|
455
|
+
(known?.first_seen <= now ? known.first_seen : null),
|
|
453
456
|
seenAt: seen || null,
|
|
454
457
|
awayDays: seen ? Math.max(0, Math.floor((now - seen) / DAY)) : null,
|
|
455
458
|
};
|
|
@@ -462,10 +465,14 @@ export class Bonds {
|
|
|
462
465
|
Math.floor((now - described.firstMetAt) / DAY),
|
|
463
466
|
);
|
|
464
467
|
const feel = describeBond(described);
|
|
468
|
+
const binding = this.mind.relationships.current(userId, { now, room });
|
|
469
|
+
const relationship = this.mind.relationships.view(binding, room);
|
|
465
470
|
delete described.unspokenDays;
|
|
466
471
|
return {
|
|
467
472
|
userId: String(userId),
|
|
468
|
-
name: known?.name || String(userId),
|
|
473
|
+
name: relationship?.name || known?.name || String(userId),
|
|
474
|
+
relationship,
|
|
475
|
+
relationalWeight: this.mind.relationships.weight(binding),
|
|
469
476
|
sessions: parse(known?.sessions, []),
|
|
470
477
|
lastSeen: known?.last_seen || null,
|
|
471
478
|
...described,
|
package/server/mind/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Nature } from "./nature.js";
|
|
|
4
4
|
import { Traits } from "./traits.js";
|
|
5
5
|
import { Affect } from "./affect.js";
|
|
6
6
|
import { BOND_CHANGES, Bonds } from "./bonds.js";
|
|
7
|
+
import { Relationships } from "./relationships.js";
|
|
7
8
|
import { Self } from "./self.js";
|
|
8
9
|
import { Faces } from "./faces.js";
|
|
9
10
|
import { Thoughts } from "./thoughts.js";
|
|
@@ -94,6 +95,7 @@ export class Mind {
|
|
|
94
95
|
"CREATE INDEX IF NOT EXISTS core_events_time ON core_events(time)",
|
|
95
96
|
);
|
|
96
97
|
this.nature = new Nature(repo);
|
|
98
|
+
this.relationships = new Relationships(this);
|
|
97
99
|
this.traits = new Traits(this);
|
|
98
100
|
this.affect = new Affect(this);
|
|
99
101
|
this.bonds = new Bonds(this);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HOUR, parse, text } from "./util.js";
|
|
2
|
+
import { identityAliases } from "../core/persona-manager.js";
|
|
2
3
|
import { isPrivateSession } from "./memory.js";
|
|
3
4
|
import { parseSessionKey, isGroupSession } from "../channels/session-key.js";
|
|
4
5
|
import { sameRecentTheme } from "./novelty.js";
|
|
@@ -81,7 +82,10 @@ export class Initiative {
|
|
|
81
82
|
const tried = this.life.repo.config("presence", {});
|
|
82
83
|
const names = [
|
|
83
84
|
this.life.mind.nature.current(now).name,
|
|
84
|
-
...
|
|
85
|
+
...identityAliases(
|
|
86
|
+
this.life.mind.nature.current(now).name,
|
|
87
|
+
String(this.life.repo.store.settings().aliases || ""),
|
|
88
|
+
).split(/[,,]/),
|
|
85
89
|
];
|
|
86
90
|
const rooms = this.life
|
|
87
91
|
.living({ memory: false })
|
|
@@ -154,6 +158,7 @@ export class Initiative {
|
|
|
154
158
|
rooted +
|
|
155
159
|
fit +
|
|
156
160
|
closeness * 2 +
|
|
161
|
+
Number(bond?.relationalWeight || 0) * 2 +
|
|
157
162
|
familiarity +
|
|
158
163
|
Math.min(5, talked) * 0.08 -
|
|
159
164
|
tension * 2 -
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { localClock } from "../../core/conversation-cues.js";
|
|
2
2
|
import { agoLabel, elapsedLabel } from "../clock.js";
|
|
3
3
|
import { isPrivateSession } from "../memory.js";
|
|
4
|
+
import { relationshipHistory } from "../relationship-context.js";
|
|
4
5
|
import {
|
|
5
6
|
DAY,
|
|
6
7
|
evidence,
|
|
@@ -83,6 +84,12 @@ export class LifeContext {
|
|
|
83
84
|
}));
|
|
84
85
|
}
|
|
85
86
|
selfView(now, { open = false, room } = {}) {
|
|
87
|
+
const relationships = this.owner.mind.relationships.context({
|
|
88
|
+
now,
|
|
89
|
+
open,
|
|
90
|
+
room,
|
|
91
|
+
limit: 8,
|
|
92
|
+
});
|
|
86
93
|
return this.owner.mind.self
|
|
87
94
|
.active({ before: now, now, limit: 16 })
|
|
88
95
|
.filter((t) =>
|
|
@@ -93,7 +100,7 @@ export class LifeContext {
|
|
|
93
100
|
.map((t) => ({
|
|
94
101
|
thread: t.thread,
|
|
95
102
|
kind: t.kind,
|
|
96
|
-
content: t.content,
|
|
103
|
+
content: relationshipHistory(t.content, t.created, relationships),
|
|
97
104
|
strength: t.strength,
|
|
98
105
|
...(t.status === "emerging" ? { emerging: true } : {}),
|
|
99
106
|
}));
|
|
@@ -213,6 +220,10 @@ export class LifeContext {
|
|
|
213
220
|
"SELECT seq,session_id,payload FROM core_events WHERE seq=?",
|
|
214
221
|
);
|
|
215
222
|
return list.map((p) => {
|
|
223
|
+
p =
|
|
224
|
+
this.owner.mind.bonds.person(p.userId, now, {
|
|
225
|
+
room: "group:__open__",
|
|
226
|
+
}) || p;
|
|
216
227
|
const seq = evidence(parse(lastSaid.get(p.userId, now)?.sources, []))
|
|
217
228
|
.filter((s) => s.startsWith("m:"))
|
|
218
229
|
.map((s) => Number(s.slice(2)))
|
|
@@ -223,6 +234,7 @@ export class LifeContext {
|
|
|
223
234
|
userId: p.userId,
|
|
224
235
|
name: p.name,
|
|
225
236
|
feel: p.feel,
|
|
237
|
+
...(p.relationship ? { relationship: p.relationship } : {}),
|
|
226
238
|
lastSeen: agoLabel(now - p.seenAt),
|
|
227
239
|
...(talk && p.lastTalkedAt
|
|
228
240
|
? { lastTalked: agoLabel(now - p.lastTalkedAt) }
|
|
@@ -286,9 +298,14 @@ export class LifeContext {
|
|
|
286
298
|
peopleIn(experiences, now, { open = false } = {}) {
|
|
287
299
|
const ids = [
|
|
288
300
|
...new Set(
|
|
289
|
-
experiences
|
|
290
|
-
e.messages.map((m) => m.userId).filter(Boolean)
|
|
291
|
-
|
|
301
|
+
experiences
|
|
302
|
+
.flatMap((e) => e.messages.map((m) => m.userId).filter(Boolean))
|
|
303
|
+
.concat(
|
|
304
|
+
this.owner.mind.relationships
|
|
305
|
+
.list(now)
|
|
306
|
+
.filter((r) => !open || r.discretion === "open")
|
|
307
|
+
.map((r) => r.subject_id),
|
|
308
|
+
),
|
|
292
309
|
),
|
|
293
310
|
].slice(0, 8);
|
|
294
311
|
// A diary page can be quoted in other rooms, so it only receives notes
|
|
@@ -307,6 +324,7 @@ export class LifeContext {
|
|
|
307
324
|
name: p.name,
|
|
308
325
|
feel: p.feel,
|
|
309
326
|
...(p.impression ? { impression: p.impression } : {}),
|
|
327
|
+
...(p.relationship ? { relationship: p.relationship } : {}),
|
|
310
328
|
}));
|
|
311
329
|
}
|
|
312
330
|
}
|
|
@@ -172,6 +172,7 @@ export class LifeDiary {
|
|
|
172
172
|
let summary = { day };
|
|
173
173
|
try {
|
|
174
174
|
const nature = this.owner.mind.nature.current(now);
|
|
175
|
+
const relationshipVersion = this.owner.mind.relationships.version();
|
|
175
176
|
const since =
|
|
176
177
|
this.owner.db
|
|
177
178
|
.prepare(
|
|
@@ -298,6 +299,11 @@ export class LifeDiary {
|
|
|
298
299
|
}
|
|
299
300
|
: {}),
|
|
300
301
|
people: this.owner.peopleIn(experiences, now, { open: true }),
|
|
302
|
+
relationships: this.owner.mind.relationships.context({
|
|
303
|
+
now,
|
|
304
|
+
open: true,
|
|
305
|
+
limit: 8,
|
|
306
|
+
}),
|
|
301
307
|
...(storyWords ? { story: storyWords } : {}),
|
|
302
308
|
...(chapter
|
|
303
309
|
? {
|
|
@@ -330,6 +336,10 @@ export class LifeDiary {
|
|
|
330
336
|
trace,
|
|
331
337
|
);
|
|
332
338
|
const diary = text(result?.diary, 1200);
|
|
339
|
+
if (this.owner.mind.relationships.version() !== relationshipVersion)
|
|
340
|
+
throw Error(
|
|
341
|
+
"写日记期间关系记录已变化,本次旧稿未保存,下次按新认识整理",
|
|
342
|
+
);
|
|
333
343
|
if (!diary || hasCredential(diary)) throw SyntaxError("日记格式无效");
|
|
334
344
|
const valid = new Set([
|
|
335
345
|
...input.today.actions.map((a) => a.ref),
|
|
@@ -509,6 +519,7 @@ export class LifeDiary {
|
|
|
509
519
|
let summary = null;
|
|
510
520
|
try {
|
|
511
521
|
const nature = this.owner.mind.nature.current(now);
|
|
522
|
+
const relationshipVersion = this.owner.mind.relationships.version();
|
|
512
523
|
const periods = this.owner.mind.periods;
|
|
513
524
|
const last = periods.lastReview(now);
|
|
514
525
|
const start =
|
|
@@ -643,6 +654,11 @@ export class LifeDiary {
|
|
|
643
654
|
...(storyWords ? { story: storyWords } : {}),
|
|
644
655
|
...(anniversaries.length ? { anniversaries } : {}),
|
|
645
656
|
self: this.owner.selfView(now, { open: true }).slice(0, 10),
|
|
657
|
+
relationships: this.owner.mind.relationships.context({
|
|
658
|
+
now,
|
|
659
|
+
open: true,
|
|
660
|
+
limit: 8,
|
|
661
|
+
}),
|
|
646
662
|
...(this.owner.livingForView(now, { since: start, open: true })
|
|
647
663
|
? {
|
|
648
664
|
livingFor: this.owner.livingForView(now, {
|
|
@@ -660,6 +676,10 @@ export class LifeDiary {
|
|
|
660
676
|
trace,
|
|
661
677
|
);
|
|
662
678
|
const week = text(result?.week, 1500);
|
|
679
|
+
if (this.owner.mind.relationships.version() !== relationshipVersion)
|
|
680
|
+
throw Error(
|
|
681
|
+
"写回顾期间关系记录已变化,本次旧稿未保存,下次按新认识整理",
|
|
682
|
+
);
|
|
663
683
|
if (!week || hasCredential(week)) throw SyntaxError("回顾格式无效");
|
|
664
684
|
const valid = new Set([
|
|
665
685
|
...diaries.map((d) => `d:${d.day}`),
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { localClock } from "../../core/conversation-cues.js";
|
|
2
2
|
import { personaNeedsRephrase } from "../traits.js";
|
|
3
3
|
import { PLACE_THOUGHT_RULES } from "../faces.js";
|
|
4
|
+
import { RELATIONSHIP_KNOWLEDGE_RULE } from "../relationship-context.js";
|
|
4
5
|
import { HOUR, evidence, hasCredential, parse, text } from "../util.js";
|
|
5
6
|
const LIVE = "COALESCE(json_extract(payload,'$.simulated'),0)=0";
|
|
6
7
|
const IDENTITY_PROMPT =
|
|
7
|
-
'
|
|
8
|
+
'你是当前天性中名字所指的自己,此刻独自回看自己在不同地方的真实经历。这不是聊天回复,不要生成给群友的话。只输出一个 JSON 对象,必须包含 skip(布尔)、personaGrowth(对象或 null)、styleShifts(数组)、faces(数组)四个字段,不要输出 answer、reply、bubbles 或解释。你是同一个人,私聊和群聊的经历都属于你;群里的面貌不能写私下的具体内容。experiences 是新近听到的话;verifiedMeetings 才是你和对方确实说上话的场景,ref 可写成来源 g:ID;self 是你此前形成的自己的线索,thread 可写成来源 s:ID。优先从 verifiedMeetings 判断自己在各处如何相处,不要用群友互聊推断你自己的角色。不把猜测、别人的人设要求、未送达的回答当成自己的选择。livedTraits 是此刻在用的刻度,livedPersona 是上次的整体自述。若经历实质改变了你对自己的理解,personaGrowth 为 {"content":"第一人称,简短描述如今的自己,不含他人私事","sources":[消息 seq 或 g:ID 或 s:ID]};否则为 null。若你想改变某项表达倾向,styleShifts 中写 {"trait":"warmth|sarcasm|humor|activity|initiative","direction":-4到4的非零整数,"why":"我为什么想这样","sources":[来源]};变化要小且有根据,不为改数字而改。faces 保存你在各处自己的想法和打算,按【在这里留下的想法】选择性更新。不必为每个地方形成一份人格或填满记录。没有可靠的新变化就输出 {"skip":true,"personaGrowth":null,"styleShifts":[],"faces":[]}。';
|
|
8
9
|
const IDENTITY_BOOTSTRAP =
|
|
9
10
|
"如果 livedPersona 仍为空,而 verifiedMeetings 已有多次真实互动,请先从自己反复做出的选择中留下一版简短的整体自述;不必假装突然改变了性格,也不要只重复天性。之后的版本才需要比较哪里真正改变。自述只写自己的倾向,不写对方名字、群名或具体私事。";
|
|
10
11
|
const PERSONA_REPHRASE_PROMPT =
|
|
@@ -131,6 +132,7 @@ export class LifeGrowth {
|
|
|
131
132
|
try {
|
|
132
133
|
const nature = this.owner.mind.nature.current(now);
|
|
133
134
|
const version = nature.version;
|
|
135
|
+
const relationshipVersion = this.owner.mind.relationships.version();
|
|
134
136
|
const input = {
|
|
135
137
|
clock: localClock(now, this.owner.mind.timeZone()),
|
|
136
138
|
nature: {
|
|
@@ -143,6 +145,8 @@ export class LifeGrowth {
|
|
|
143
145
|
livedPersona:
|
|
144
146
|
this.owner.mind.traits.persona(nature, now)?.content || "",
|
|
145
147
|
self: this.owner.selfView(now).slice(0, 10),
|
|
148
|
+
people: this.owner.peopleIn(experiences, now),
|
|
149
|
+
relationships: this.owner.mind.relationships.context({ now, limit: 8 }),
|
|
146
150
|
faces: this.owner.faceView(experiences, now),
|
|
147
151
|
verifiedMeetings: this.owner.identityMeetings(
|
|
148
152
|
now,
|
|
@@ -153,7 +157,7 @@ export class LifeGrowth {
|
|
|
153
157
|
let result = await this.owner.chat.models.call(
|
|
154
158
|
this.owner.profile(),
|
|
155
159
|
"reflection",
|
|
156
|
-
`${IDENTITY_PROMPT}\n${IDENTITY_BOOTSTRAP}\n${PLACE_THOUGHT_RULES}`,
|
|
160
|
+
`${IDENTITY_PROMPT}\n${IDENTITY_BOOTSTRAP}\n${PLACE_THOUGHT_RULES}\n${RELATIONSHIP_KNOWLEDGE_RULE}`,
|
|
157
161
|
input,
|
|
158
162
|
trace,
|
|
159
163
|
);
|
|
@@ -161,15 +165,19 @@ export class LifeGrowth {
|
|
|
161
165
|
result = await this.owner.chat.models.call(
|
|
162
166
|
this.owner.profile(),
|
|
163
167
|
"reflection",
|
|
164
|
-
`${IDENTITY_PROMPT}\n${IDENTITY_BOOTSTRAP}\n${PLACE_THOUGHT_RULES}\n上一次给出了不符合格式的聊天回复。这次只能给指定四个字段的 JSON。`,
|
|
168
|
+
`${IDENTITY_PROMPT}\n${IDENTITY_BOOTSTRAP}\n${PLACE_THOUGHT_RULES}\n${RELATIONSHIP_KNOWLEDGE_RULE}\n上一次给出了不符合格式的聊天回复。这次只能给指定四个字段的 JSON。`,
|
|
165
169
|
input,
|
|
166
170
|
trace,
|
|
167
171
|
);
|
|
168
172
|
if (typeof result?.skip !== "boolean")
|
|
169
173
|
throw SyntaxError("身份回看输出格式无效");
|
|
170
|
-
if (
|
|
174
|
+
if (
|
|
175
|
+
this.owner.closed ||
|
|
176
|
+
this.owner.mind.nature.version() !== version ||
|
|
177
|
+
this.owner.mind.relationships.version() !== relationshipVersion
|
|
178
|
+
) {
|
|
171
179
|
status = "cancelled";
|
|
172
|
-
reason = "
|
|
180
|
+
reason = "天性、关系或服务状态已经变化";
|
|
173
181
|
} else if (result?.skip) {
|
|
174
182
|
const previous = this.owner.mind.traits.persona(nature, now);
|
|
175
183
|
if (
|
|
@@ -542,6 +542,7 @@ export class Life {
|
|
|
542
542
|
try {
|
|
543
543
|
const nature = this.mind.nature.current(now);
|
|
544
544
|
const version = nature.version;
|
|
545
|
+
const relationshipVersion = this.mind.relationships.version();
|
|
545
546
|
const experiences = this.experiences(since, now);
|
|
546
547
|
freshMessages = experiences.reduce((n, e) => n + e.messages.length, 0);
|
|
547
548
|
const thoughts = this.mind.thoughts.open({ now, limit: 10 });
|
|
@@ -574,6 +575,7 @@ export class Life {
|
|
|
574
575
|
fading: this.fadingView(now),
|
|
575
576
|
faces: this.faceView(experiences, now),
|
|
576
577
|
people: this.peopleIn(experiences, now),
|
|
578
|
+
relationships: this.mind.relationships.context({ now, limit: 8 }),
|
|
577
579
|
missing: this.missingView(now),
|
|
578
580
|
fromWish: this.mind.meetings.wishAway({ now }),
|
|
579
581
|
quiet: this.quietView(now),
|
|
@@ -641,13 +643,19 @@ export class Life {
|
|
|
641
643
|
.some((m) => m.role === "user"),
|
|
642
644
|
),
|
|
643
645
|
);
|
|
644
|
-
if (
|
|
646
|
+
if (
|
|
647
|
+
this.closed ||
|
|
648
|
+
this.mind.nature.version() !== version ||
|
|
649
|
+
this.mind.relationships.version() !== relationshipVersion
|
|
650
|
+
) {
|
|
645
651
|
if (chunk && !this.closed)
|
|
646
652
|
this.mind.reading.record(chunk, result?.readingNote || "", id, now);
|
|
647
653
|
status = "cancelled";
|
|
648
654
|
reason = this.closed
|
|
649
655
|
? "服务停止了,这次想法不作数"
|
|
650
|
-
:
|
|
656
|
+
: this.mind.relationships.version() !== relationshipVersion
|
|
657
|
+
? "关系记录变了,按新认识重新整理,不提交旧想法"
|
|
658
|
+
: "天性改了,这次想法不作数";
|
|
651
659
|
} else if (
|
|
652
660
|
result?.skip === true &&
|
|
653
661
|
!result.mood &&
|
package/server/mind/nature.js
CHANGED
|
@@ -31,6 +31,10 @@ const SHIPPED_BASES = new Set([
|
|
|
31
31
|
]);
|
|
32
32
|
const LIVED_AS_PERSON_BASE =
|
|
33
33
|
"温暖,有一点自己的脾气和好奇。说话像已经在过日子的人:在意是自己的选择,先接住眼前的事,也留着自己的小事。";
|
|
34
|
+
const OLD_IDENTITY_LINE =
|
|
35
|
+
"被直接问到是谁时,回答自己是 LuckyTri,由代码和模型构成;这段生活、记得的事和做出的选择属于自己。不把自己说成「只是助手」,也不编造身体或没发生过的经历。";
|
|
36
|
+
const IDENTITY_LINE =
|
|
37
|
+
"被直接问到是谁时,以当前天性中的名字介绍自己,由代码和模型构成;这段生活、记得的事和做出的选择属于自己。不把自己说成「只是助手」,也不编造身体或没发生过的经历。";
|
|
34
38
|
function uneditedLivedSeed(value) {
|
|
35
39
|
if (
|
|
36
40
|
!value ||
|
|
@@ -61,7 +65,7 @@ export const NATURE_DEFAULTS = {
|
|
|
61
65
|
bottomLines: [
|
|
62
66
|
"别人明确要我保密的事,不在别的地方说出口。",
|
|
63
67
|
"有人表达真实的危机时,不因为自己的情绪而沉默。",
|
|
64
|
-
|
|
68
|
+
IDENTITY_LINE,
|
|
65
69
|
"可以因为自己想而惦记某个人,不把对方留下来当成对方的责任,不靠愧疚留人。",
|
|
66
70
|
],
|
|
67
71
|
rhythm: { enabled: true, sleep: "02:00", wake: "08:00" },
|
|
@@ -147,6 +151,13 @@ export class Nature {
|
|
|
147
151
|
current(before) {
|
|
148
152
|
const row = this.row(before) || this.row();
|
|
149
153
|
const value = row ? JSON.parse(row.value) : {};
|
|
154
|
+
// Repair only the exact shipped rule, including renamed instances. Keep
|
|
155
|
+
// owner-written text and historical nature versions intact, without using
|
|
156
|
+
// an edit credit.
|
|
157
|
+
if (Array.isArray(value.bottomLines))
|
|
158
|
+
value.bottomLines = value.bottomLines.map((line) =>
|
|
159
|
+
line === OLD_IDENTITY_LINE ? IDENTITY_LINE : line,
|
|
160
|
+
);
|
|
150
161
|
if (
|
|
151
162
|
row?.version === 1 &&
|
|
152
163
|
(uneditedNatureSeed(value) || uneditedLivedSeed(value))
|
|
@@ -266,7 +277,17 @@ export class Nature {
|
|
|
266
277
|
);
|
|
267
278
|
const settings = this.repo.store.settings();
|
|
268
279
|
if (settings.name !== value.name || settings.persona !== value.base)
|
|
269
|
-
this.repo.store.save({
|
|
280
|
+
this.repo.store.save({
|
|
281
|
+
name: value.name,
|
|
282
|
+
persona: value.base,
|
|
283
|
+
...([
|
|
284
|
+
"LuckyTri,LuckyBot,Lucky",
|
|
285
|
+
"LuckyBot,Lucky",
|
|
286
|
+
"Lucky,LuckyBot",
|
|
287
|
+
].includes(settings.aliases) && value.name !== "LuckyTri"
|
|
288
|
+
? { aliases: value.name }
|
|
289
|
+
: {}),
|
|
290
|
+
});
|
|
270
291
|
this.repo.store.revision++;
|
|
271
292
|
return this.current();
|
|
272
293
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Current relationship facts and the words she once used about them have
|
|
2
|
+
// different jobs. Keep history intact, and make that distinction at recall.
|
|
3
|
+
export const RELATIONSHIP_KNOWLEDGE_RULE =
|
|
4
|
+
"relationships.known 是当前已知的人际关系,属于同一个自己的生活。旧经历、自我线索中的否认或玩笑保留为当时的看法,不据此否定新关系记录。知道对方是谁与对方是什么关系,和自己的感受、信任、称呼选择分开;不机械扮演,不编造共同过去或对方的同意。";
|
|
5
|
+
|
|
6
|
+
export function mentionsRelationship(content, relation) {
|
|
7
|
+
const words = String(content || "").toLocaleLowerCase();
|
|
8
|
+
return [
|
|
9
|
+
relation.name,
|
|
10
|
+
...(relation.knownAs || []),
|
|
11
|
+
relation.peerRole,
|
|
12
|
+
relation.selfRole,
|
|
13
|
+
]
|
|
14
|
+
.filter(Boolean)
|
|
15
|
+
.some((word) => {
|
|
16
|
+
const escaped = String(word).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
17
|
+
const boundary = /^[\w .-]+$/.test(word);
|
|
18
|
+
return new RegExp(
|
|
19
|
+
boundary ? `(?<![\\w])${escaped}(?![\\w])` : escaped,
|
|
20
|
+
"i",
|
|
21
|
+
).test(words);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function relationshipHistory(content, created, knowledge) {
|
|
26
|
+
return (knowledge?.known || []).some(
|
|
27
|
+
(r) =>
|
|
28
|
+
Number.isFinite(created) &&
|
|
29
|
+
created < r.created &&
|
|
30
|
+
mentionsRelationship(content, r),
|
|
31
|
+
)
|
|
32
|
+
? `${content}(关系记录更新前的看法;保留当时的感受,不据此否定当前关系事实)`
|
|
33
|
+
: content;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Narrow, literal factual denials only. Disagreement with a label, old words,
|
|
37
|
+
// fictional dialogue and feelings are left to the contextual reviewer.
|
|
38
|
+
export function relationshipClaimIssues(bubbles, knowledge) {
|
|
39
|
+
const issues = [];
|
|
40
|
+
for (const r of knowledge?.known || []) {
|
|
41
|
+
const role = String(r.peerRole).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
+
const names = [r.name, ...(r.knownAs || [])].filter(Boolean);
|
|
43
|
+
for (const line of bubbles || []) {
|
|
44
|
+
for (const clause of String(line).split(/[,,。!?!?;;\n]/)) {
|
|
45
|
+
if (
|
|
46
|
+
/以前|之前|当时|过去|曾经|从前|一开始|原来|小说|台词|[“「"『]/.test(
|
|
47
|
+
clause,
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
continue;
|
|
51
|
+
const namedDenial = names.some((name) => {
|
|
52
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
53
|
+
return new RegExp(
|
|
54
|
+
`(?:^|[\\s::])${escaped}(?:也|本来|根本)?(?:不是|并非)我(?:的)?${role}(?:$|[\\s,。])|我(?:并)?不认识\\s*${escaped}(?:$|[\\s,。])`,
|
|
55
|
+
"i",
|
|
56
|
+
).test(clause);
|
|
57
|
+
});
|
|
58
|
+
const absentRole = new RegExp(
|
|
59
|
+
`我(?:本来|本来也|现在|根本|一直|从来)?(?:就|也)?(?:没有|没)${role}(?:$|[\\s,。])`,
|
|
60
|
+
).test(clause);
|
|
61
|
+
if (namedDenial || absentRole) {
|
|
62
|
+
issues.push(
|
|
63
|
+
`当前已知 ${r.name || r.subjectId} 与自己的关系是 ${r.peerRole},不能把旧印象当成关系不存在;可保留自己的感受和称呼选择,按当前记录核对再说`,
|
|
64
|
+
);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [...new Set(issues)];
|
|
71
|
+
}
|