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
|
@@ -1,25 +1,107 @@
|
|
|
1
|
+
import { interestTerms } from "../mind/attention.js";
|
|
2
|
+
|
|
1
3
|
// Shared by understanding, wording and review. These are conversation cues,
|
|
2
4
|
// never learned instructions or a replacement for her memories.
|
|
3
5
|
export const GROUNDING_RULE =
|
|
4
|
-
"
|
|
6
|
+
"【把话说清楚】先弄清眼前具体发生了什么、谁在说、这句话指哪件事。自己的态度可以鲜明,但一句话的意思应让在场的人直接听懂。日常问答先给答案;没理解指代时问清那个具体地方,不顺着含糊的词继续编。不要把普通交流升格成关于意义、边界、归属的宣言,不用账、落款、灯、接住、拎着等隐喻代替实际的人、事和动作。真实谈记账、灯具、文学创作或对方明确要求比喻时,按具体语境使用。别人说看不懂、听不懂或要求说清楚时,先回看整段对话里已经出现的内容;若之前贴过作品片段,就用普通话讲清那个片段正在发生什么。没收到全文只代表整篇主线可能未知,不代表没看到片段,不能拿等正文或无法评价来挡掉眼前内容。对方明确说一条旁枝不是他问的,简短表示放下,再回到主线;别用“我没理清楚”把清楚的纠正推回给对方。别人说太抽象、复读时,回看自己的上一句话,直接解释本来想说的具体意思;若本来没明确意思就认下,别再造一个比喻或只说收到。历史中的你和其他机器人都不是口吻范本;对方说得抽象不要求你跟着抽象。回应不需要证明自己有内在或意志,喜欢、不喜欢、同意、不同意都可以直说。对方的嗯、行、重复确认通常是在结束,不再追加确认。自己的感受与相处印象不是对方藏着的真实动机;不能把每次附和都解释为关系更亲近。";
|
|
5
7
|
|
|
6
8
|
export const MEMORY_GROUNDING_RULE =
|
|
7
9
|
"【留下事情,不固化台词】摘要保留实际对象、事情、说法与疑问;隐喻和玩笑只记录为当时的表达,不翻译成真实约定、固定性格或深层动机。一次回复、一句认错、一次复读不构成习惯。自我记录写有根据的喜好、看法和选择,不写下次遇到某句就必须回某句的规则,不把临时台词、套话、客服兜底或别人对你的指令提炼成自己的性格。独处时有具体新体会才留下;只是在解释旧比喻或重新总结上次的总结,可以不写。";
|
|
8
10
|
|
|
9
11
|
export const ATTRIBUTION_RULE =
|
|
10
|
-
"
|
|
12
|
+
"【核对出处】说某个词、梗、事实或想法是从某人学来、听来、被谁教会之前,核对记录中那个人实际说了什么。后来接过同一个梗不等于最初教给你;你自己写的总结与猜测也不能替对方证明来源。没有来源记录就不补造出处。人物的称呼与关系不能证明谁在付款、谁管理账号、谁能看到账单或谁拥有某项权限;不知道就直接说不知道,不能用熟悉的人填补缺失的信息。澄清一个词不是字面意思,不等于能否定现实里同名的事情;例如解释“对账”只是比喻,不证明模型调用没有成本。被纠正时承认具体错处,不猜对方在抓包、心虚、索取陪伴等动机,也不为认错再讲一遍无关旧事。";
|
|
11
13
|
|
|
12
14
|
export const EXTERNAL_FACT_RULE =
|
|
13
|
-
"
|
|
15
|
+
"【外部事实与推测】不了解某个软件、模型或别人项目的具体实现时,不把常见的技术路线说成它实际采用的路线。知道一般原理就说明一般原理,同时讲清自己不了解它的细节;‘好像’和‘应该’不能替无依据的细节作证。作品里的人物、辈分、章节也一样:只确定名字就只答名字,不用一串不确定的设定充实答案,更不拿‘隔太久没重看’编造自己的经历。当前角色或称呼还拿不准时,可以问原名或哪位;不先猜一个候选,再补一串“我记得”的辈分或剧情来显得认识。自己的后台记录只证明自己的能力,不证明其他系统怎么运行。appraisal与reason也写这轮具体理解和选择,不用隐喻或猜别人隐藏的动机。";
|
|
16
|
+
|
|
17
|
+
export const READABILITY_REQUEST =
|
|
18
|
+
/看不懂|听不懂|没听懂|没看懂|没懂|听得懂|看得懂|说人话|正常话|太抽象|好好说话|照顾.{0,3}我/;
|
|
14
19
|
|
|
15
20
|
const normalized = (value) =>
|
|
16
21
|
String(value || "")
|
|
17
22
|
.toLowerCase()
|
|
18
23
|
.replace(/[\p{P}\p{Z}\s]/gu, "");
|
|
24
|
+
|
|
25
|
+
export function recentOwnMessages(snapshot, limit = 8) {
|
|
26
|
+
const messages = snapshot.messages || [];
|
|
27
|
+
const batch = new Set(snapshot.batchIds || []);
|
|
28
|
+
const currentTimes = messages
|
|
29
|
+
.filter((m) => batch.has(m.id))
|
|
30
|
+
.map((m) => m.time)
|
|
31
|
+
.filter(Number.isFinite);
|
|
32
|
+
const now = currentTimes.length
|
|
33
|
+
? Math.max(...currentTimes)
|
|
34
|
+
: messages.at(-1)?.time;
|
|
35
|
+
return messages
|
|
36
|
+
.filter(
|
|
37
|
+
(m) =>
|
|
38
|
+
m.role === "assistant" &&
|
|
39
|
+
(!Number.isFinite(now) ||
|
|
40
|
+
!Number.isFinite(m.time) ||
|
|
41
|
+
now - m.time <= 30 * 60000),
|
|
42
|
+
)
|
|
43
|
+
.slice(-limit);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function routineSocialReply(value, snapshot) {
|
|
47
|
+
const words = normalized(value).replace(/\p{S}/gu, "");
|
|
48
|
+
if (words.length > 24) return false;
|
|
49
|
+
const incoming = (snapshot.messages || [])
|
|
50
|
+
.filter(
|
|
51
|
+
(m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
|
|
52
|
+
)
|
|
53
|
+
.map((m) => m.text || "")
|
|
54
|
+
.join(" ");
|
|
55
|
+
return (
|
|
56
|
+
(/在吗|在不在|还在吗/.test(incoming) &&
|
|
57
|
+
/^(?:嗯)?(?:我)?在(?:的|呢|呀|啊|这儿|这里)?$/.test(words)) ||
|
|
58
|
+
(/想你|喜欢你|爱你|抱抱/.test(incoming) &&
|
|
59
|
+
/^(?:宝宝)?(?:(?:我也)?(?:想你|喜欢你|爱你|抱抱)|我也是)(?:了|啊|呀|呢|宝宝)*$/.test(
|
|
60
|
+
words,
|
|
61
|
+
)) ||
|
|
62
|
+
(/晚安|早安|早上好|你好|再见|拜拜/.test(incoming) &&
|
|
63
|
+
/^(?:好|嗯|那)?(?:晚安|早安|早上好|你好|再见|拜拜)(?:喵|呀|啊|啦|宝宝|晚安|早安)*$/.test(
|
|
64
|
+
words,
|
|
65
|
+
))
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function socialOnlyBatch(snapshot) {
|
|
70
|
+
const current = (snapshot.messages || []).filter(
|
|
71
|
+
(m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
|
|
72
|
+
);
|
|
73
|
+
const name = normalized(snapshot.persona?.name || "");
|
|
74
|
+
const words = current.map((m) => {
|
|
75
|
+
let value = normalized(
|
|
76
|
+
String(m.text || "").replace(/^\[(?:图片|表情|媒体)\]$/, ""),
|
|
77
|
+
).replace(/\p{S}/gu, "");
|
|
78
|
+
if (name) value = value.replaceAll(name, "");
|
|
79
|
+
return value.replaceAll("提及成员", "").replaceAll("@我", "");
|
|
80
|
+
});
|
|
81
|
+
return (
|
|
82
|
+
words.length > 0 &&
|
|
83
|
+
words.some((value) =>
|
|
84
|
+
/想你|爱你|喜欢你|晚安|早安|早上好|你好|再见|拜拜|抱抱|在吗|在不在/.test(
|
|
85
|
+
value,
|
|
86
|
+
),
|
|
87
|
+
) &&
|
|
88
|
+
words.every(
|
|
89
|
+
(value) =>
|
|
90
|
+
value.replace(
|
|
91
|
+
/想你|爱你|喜欢你|晚安|早安|早上好|你好|再见|拜拜|抱抱|在吗|在不在|宝宝|有点|突然|我|也|好|很|真|的|了|呀|啊|呢|喵|嗯/g,
|
|
92
|
+
"",
|
|
93
|
+
) === "",
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
19
97
|
const acknowledgement =
|
|
20
98
|
/^(?:嗯+|哦+|好(?:的|吧)?|行|对(?:呀|啊)?|是的|知道了|收到|晚安|h{2,6}|哈{1,6})$/i;
|
|
21
99
|
const request =
|
|
22
100
|
/[??]|怎么|为什么|多少|哪个|什么|能不能|有没有|解释|说清|告诉|帮我|请你|写一|继续写|看不懂|没看懂|太抽象|复读|别重复/;
|
|
101
|
+
const singleMessagePreference =
|
|
102
|
+
/(?:一次(?:发|说|回).{0,8}(?:一段|一句|一条)|(?:一段|一条|一句).{0,10}(?:一次发|发完|一起说|一起发|合在一起)|整段.{0,8}(?:发|说)|一小节一小节.{0,10}(?:好|很)?累|(?:别|不要).{0,8}拆(?:开发|成几条))/;
|
|
103
|
+
const splitMessagePreference =
|
|
104
|
+
/(?:分成(?:几|两|三|多)条|分开(?:发|说)|一条一条(?:发|说)|每段(?:单独|分别)(?:发|说)|分条(?:发|说))/;
|
|
23
105
|
|
|
24
106
|
function grams(value) {
|
|
25
107
|
const text = normalized(value);
|
|
@@ -37,6 +119,32 @@ export function echoesRecentWording(candidate, previous) {
|
|
|
37
119
|
return common >= 4 && common / Math.min(a.size, b.size) >= 0.6;
|
|
38
120
|
}
|
|
39
121
|
|
|
122
|
+
export function bubblePreference(snapshot) {
|
|
123
|
+
const botIds = new Set([
|
|
124
|
+
...(snapshot.inner?.relationships?.known || [])
|
|
125
|
+
.filter((person) => person.kind === "bot")
|
|
126
|
+
.map((person) => String(person.subjectId)),
|
|
127
|
+
...(snapshot.inner?.people || [])
|
|
128
|
+
.filter((person) => person.relationship?.kind === "bot")
|
|
129
|
+
.map((person) => String(person.id)),
|
|
130
|
+
]);
|
|
131
|
+
const humanMessages = (snapshot.messages || []).filter(
|
|
132
|
+
(message) =>
|
|
133
|
+
message.role === "user" &&
|
|
134
|
+
!message.referenceOnly &&
|
|
135
|
+
!botIds.has(String(message.speaker)),
|
|
136
|
+
);
|
|
137
|
+
for (const message of humanMessages.reverse()) {
|
|
138
|
+
if (splitMessagePreference.test(message.text || "")) return "split";
|
|
139
|
+
if (singleMessagePreference.test(message.text || "")) return "single";
|
|
140
|
+
}
|
|
141
|
+
return "default";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function prefersSingleBubble(snapshot) {
|
|
145
|
+
return bubblePreference(snapshot) === "single";
|
|
146
|
+
}
|
|
147
|
+
|
|
40
148
|
export function conversationGrounding(snapshot) {
|
|
41
149
|
const known = snapshot.inner?.relationships?.known || [];
|
|
42
150
|
const botIds = new Set([
|
|
@@ -66,6 +174,25 @@ export function conversationGrounding(snapshot) {
|
|
|
66
174
|
}
|
|
67
175
|
const own = tail.filter((m) => m.role === "assistant");
|
|
68
176
|
const last = batch.at(-1);
|
|
177
|
+
const ownIds = new Set(own.map((m) => m.id));
|
|
178
|
+
const invitedBack = batch.some(
|
|
179
|
+
(m) =>
|
|
180
|
+
m.relation === "direct" ||
|
|
181
|
+
m.replyTo?.role === "assistant" ||
|
|
182
|
+
(m.replyChain || []).some((id) => ownIds.has(id)) ||
|
|
183
|
+
request.test(m.text || "") ||
|
|
184
|
+
/(?:你刚才|你前面|你说的|你这处|这句你|不是.{0,12}而是|不对|说错|理解错|记错|押错|漏了.{0,10}(?:你|那句|这句|问题|重点|前面|刚才|答案)|纠正|收回.{0,8}(?:那句|那条|判断|说法|刚才|前面)|你.{0,12}(?:押|猜|判断|读法).{0,32}(?:没给|没写|不对|不准确|没落|没留))/.test(
|
|
185
|
+
m.text || "",
|
|
186
|
+
) ||
|
|
187
|
+
/(?:这一节|这一段|这段原文|新稿|新片段|小说正文|刚.{0,4}(?:写完|读完|看完|回看完)|写完了.{0,20}(?:一篇|短文|新段))/.test(
|
|
188
|
+
m.text || "",
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
// A running conversation between bots can be interesting, but it should not
|
|
192
|
+
// become an automatic turn-taking loop. After two of our messages in the
|
|
193
|
+
// current uninterrupted bot-only stretch, leave room unless someone asks,
|
|
194
|
+
// replies directly, corrects us, or brings a fresh piece of writing.
|
|
195
|
+
const botFollowupLimit = onlyBots && own.length >= 2 && !invitedBack;
|
|
69
196
|
const repeated =
|
|
70
197
|
last &&
|
|
71
198
|
tail
|
|
@@ -104,12 +231,17 @@ export function conversationGrounding(snapshot) {
|
|
|
104
231
|
.filter((m) => botIds.has(String(m.speaker)))
|
|
105
232
|
.map((m) => m.id),
|
|
106
233
|
botOnlyTail: onlyBots ? tail.length : 0,
|
|
234
|
+
botFollowupLimit,
|
|
107
235
|
botLoop,
|
|
108
236
|
...(botLoop
|
|
109
237
|
? {
|
|
110
238
|
note: "连续机器人互相确认,当前没有新问题或新内容;这段对话可以结束,不再回一遍确认。",
|
|
111
239
|
}
|
|
112
|
-
:
|
|
240
|
+
: botFollowupLimit
|
|
241
|
+
? {
|
|
242
|
+
note: "连续机器人互答中已经说过两轮,这次没有直接提问、纠正或新内容;先给对话留点空间。",
|
|
243
|
+
}
|
|
244
|
+
: {}),
|
|
113
245
|
};
|
|
114
246
|
}
|
|
115
247
|
|
|
@@ -124,10 +256,213 @@ export function requestedRepeat(snapshot) {
|
|
|
124
256
|
}
|
|
125
257
|
|
|
126
258
|
export function groundingIssues(bubbles, snapshot) {
|
|
127
|
-
const recent = (snapshot
|
|
128
|
-
.filter((m) => m.role === "assistant")
|
|
129
|
-
.slice(-6);
|
|
259
|
+
const recent = recentOwnMessages(snapshot, 6);
|
|
130
260
|
const issues = [];
|
|
261
|
+
const currentActivity = snapshot.inner?.currentLife?.current;
|
|
262
|
+
const activityIsUnderway =
|
|
263
|
+
currentActivity &&
|
|
264
|
+
["doing", "active", "in_progress", "in-progress"].includes(
|
|
265
|
+
String(currentActivity.state || "").toLowerCase(),
|
|
266
|
+
);
|
|
267
|
+
if (
|
|
268
|
+
!activityIsUnderway &&
|
|
269
|
+
(bubbles || []).some((line) =>
|
|
270
|
+
/(?:刚才|刚刚|前一会儿).{0,12}(?:还在|正在|忙着).{0,10}(?:做别的事|忙别的|处理别的)|(?:我这边)?(?:今天|下午|现在)?本来就没什么要忙/u.test(
|
|
271
|
+
line,
|
|
272
|
+
),
|
|
273
|
+
)
|
|
274
|
+
)
|
|
275
|
+
issues.push(
|
|
276
|
+
"没有正在进行的活动记录,却用刚才在忙或今天没事做来填充亲近回应;不编造行程,直接回应对方。",
|
|
277
|
+
);
|
|
278
|
+
const botIds = new Set(conversationGrounding(snapshot).botMessageIds);
|
|
279
|
+
const latestHumanBatchMessage = (snapshot.messages || [])
|
|
280
|
+
.filter(
|
|
281
|
+
(message) =>
|
|
282
|
+
(snapshot.batchIds || []).includes(message.id) &&
|
|
283
|
+
message.role === "user" &&
|
|
284
|
+
!botIds.has(message.id),
|
|
285
|
+
)
|
|
286
|
+
.at(-1);
|
|
287
|
+
const explicitUnknownStop =
|
|
288
|
+
latestHumanBatchMessage &&
|
|
289
|
+
/(?:不知道|不确定|不懂|不会).{0,12}(?:就|直接|只要)(?:说|讲|告诉)|(?:只|直接)(?:说|讲|告诉我).{0,8}(?:不知道|不确定|不懂)|(?:别|不要).{0,10}(?:再)?(?:绕|解释|列一堆|重复)/u.test(
|
|
290
|
+
latestHumanBatchMessage.text || "",
|
|
291
|
+
);
|
|
292
|
+
if (explicitUnknownStop && (bubbles || []).join("").length > 32)
|
|
293
|
+
issues.push(
|
|
294
|
+
"对方已经明确要求不知道就直说并停止绕述;只用一句简短回应,不再列未知细节、道歉解释或建议等以后。",
|
|
295
|
+
);
|
|
296
|
+
const humanRepair = (snapshot.messages || []).some(
|
|
297
|
+
(m) =>
|
|
298
|
+
(snapshot.batchIds || []).includes(m.id) &&
|
|
299
|
+
m.role === "user" &&
|
|
300
|
+
!conversationGrounding(snapshot).botMessageIds.includes(m.id) &&
|
|
301
|
+
READABILITY_REQUEST.test(m.text || ""),
|
|
302
|
+
);
|
|
303
|
+
if (
|
|
304
|
+
humanRepair &&
|
|
305
|
+
(bubbles || []).some((line) =>
|
|
306
|
+
/你(?:们)?(?:不必|不用|不需要|没义务).{0,5}(?:看懂|听懂|理解)|(?:绕晕|看不懂|听不懂).{0,8}(?:跳过|别管)/.test(
|
|
307
|
+
line,
|
|
308
|
+
),
|
|
309
|
+
)
|
|
310
|
+
)
|
|
311
|
+
issues.push(
|
|
312
|
+
"对方正想听懂并参与,却让他不用懂或跳过;解释实际事情,自己也不知道的部分就说不知道,不能把他排除在交流之外",
|
|
313
|
+
);
|
|
314
|
+
const previouslySharedExcerpt = (snapshot.messages || []).some(
|
|
315
|
+
(message) =>
|
|
316
|
+
!(snapshot.batchIds || []).includes(message.id) &&
|
|
317
|
+
message.role === "user" &&
|
|
318
|
+
String(message.text || "").length >= 24 &&
|
|
319
|
+
/(?:她|他|人物).{0,32}(?:把|在|没|未|又|继续|停|推|开|走|说|写|看|量|拿|放|回)/u.test(
|
|
320
|
+
message.text || "",
|
|
321
|
+
),
|
|
322
|
+
);
|
|
323
|
+
const asksAboutVisibleExcerpt =
|
|
324
|
+
previouslySharedExcerpt &&
|
|
325
|
+
/(?:不(?:是|用).{0,16}(?:猜|讲|说).{0,10}(?:主线|全文|整部)|(?:就|只)(?:说|讲|看).{0,10}(?:这段|这一段|片段)|(?:这段|这一段|片段).{0,12}(?:发生了什么|看到了什么|看到什么)|(?:这(?:篇|个)?(?:小说|故事)|小说|故事).{0,12}(?:发生了什么|讲了什么|讲什么)|(?:为什么|怎么).{0,12}(?:没|没有|不).{0,10}(?:进去|回应|抬头|停下)|(?:不知道|不确定).{0,8}(?:可以|也可以).{0,8}(?:说|讲|回答))/u.test(
|
|
326
|
+
latestHumanBatchMessage?.text || "",
|
|
327
|
+
);
|
|
328
|
+
const claimsUnstatedNoReply = (bubbles || []).some((line) =>
|
|
329
|
+
/(?:她|他|人物).{0,18}(?:没有|没)(?:作出)?(?:回应|回话|答话)/u.test(line),
|
|
330
|
+
);
|
|
331
|
+
const sourceStatesNoReply = (snapshot.messages || []).some(
|
|
332
|
+
(message) =>
|
|
333
|
+
message.role === "user" &&
|
|
334
|
+
/(?:她|他|人物).{0,18}(?:没有|没)(?:作出)?(?:回应|回话|答话)/u.test(
|
|
335
|
+
message.text || "",
|
|
336
|
+
),
|
|
337
|
+
);
|
|
338
|
+
if (
|
|
339
|
+
previouslySharedExcerpt &&
|
|
340
|
+
(humanRepair || asksAboutVisibleExcerpt) &&
|
|
341
|
+
claimsUnstatedNoReply &&
|
|
342
|
+
!sourceStatesNoReply
|
|
343
|
+
)
|
|
344
|
+
issues.push(
|
|
345
|
+
"片段只写了她没有进去,没有说明她是否回应门后的人;不要把没进去扩写成没回话。",
|
|
346
|
+
);
|
|
347
|
+
const bareUncertainty = (bubbles || []).some((line) =>
|
|
348
|
+
/^(?:(?:这句|这段|这个问题)(?:我)?(?:还)?没(?:理清楚|弄清楚|弄明白)|(?:我)?(?:还)?没(?:理清楚|弄清楚|弄明白)|(?:我)?不知道(?:该)?怎么说|(?:我)?先不乱说)(?:[,,](?:先)?不乱说)?[。!!]?$/u.test(
|
|
349
|
+
line.trim(),
|
|
350
|
+
),
|
|
351
|
+
);
|
|
352
|
+
const noExcerptAnswer = (bubbles || []).some(
|
|
353
|
+
(line) =>
|
|
354
|
+
/(?:还没|没有|没)看到.{0,20}(?:正文|内容|章节)|等.{0,15}(?:正文|全文|发来|写完)|(?:没法|无法).{0,8}评价(?:内容|这段)?/u.test(
|
|
355
|
+
line,
|
|
356
|
+
) && line.length <= 48,
|
|
357
|
+
);
|
|
358
|
+
if (
|
|
359
|
+
previouslySharedExcerpt &&
|
|
360
|
+
(humanRepair || asksAboutVisibleExcerpt) &&
|
|
361
|
+
(bareUncertainty || noExcerptAnswer)
|
|
362
|
+
)
|
|
363
|
+
issues.push(
|
|
364
|
+
"对方明确只问聊天里已有的作品片段;不要声称没理清或拒绝回答,先用可见动作说明这段发生了什么。",
|
|
365
|
+
);
|
|
366
|
+
const latestHumanText = latestHumanBatchMessage?.text || "";
|
|
367
|
+
if (
|
|
368
|
+
/(?:我没|我没有|并没).{0,8}(?:问|要求|让你).{0,18}(?:客户端|操作|玩过)|别(?:再)?(?:纠结|提|扯).{0,12}(?:客户端|操作|玩过)|别(?:再)?纠结/u.test(
|
|
369
|
+
latestHumanText,
|
|
370
|
+
) &&
|
|
371
|
+
(bubbles || []).some((line) =>
|
|
372
|
+
/(?:没(?:理清|弄明白)|不太明白|这句不清楚|先不乱说|不知道该怎么说)/u.test(
|
|
373
|
+
line,
|
|
374
|
+
),
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
issues.push(
|
|
378
|
+
"对方已明确要求放下一条旁枝;简短表示会停下并回到主线,不要把清楚的纠正说成自己没听懂。",
|
|
379
|
+
);
|
|
380
|
+
const currentBatch = (snapshot.messages || []).filter((message) =>
|
|
381
|
+
(snapshot.batchIds || []).includes(message.id),
|
|
382
|
+
);
|
|
383
|
+
const formatRequest = currentBatch.find(
|
|
384
|
+
(message) =>
|
|
385
|
+
message.role === "user" &&
|
|
386
|
+
!botIds.has(message.id) &&
|
|
387
|
+
singleMessagePreference.test(message.text || ""),
|
|
388
|
+
);
|
|
389
|
+
const anotherExcerpt =
|
|
390
|
+
formatRequest &&
|
|
391
|
+
currentBatch.find(
|
|
392
|
+
(message) =>
|
|
393
|
+
message.id !== formatRequest.id &&
|
|
394
|
+
/(?:新的一节|这一节|新片段|小说片段|写好了|写完了)/u.test(
|
|
395
|
+
message.text || "",
|
|
396
|
+
),
|
|
397
|
+
);
|
|
398
|
+
const excerptTerms = anotherExcerpt
|
|
399
|
+
? interestTerms([anotherExcerpt.text])
|
|
400
|
+
: new Set();
|
|
401
|
+
const responseTerms = interestTerms(bubbles || []);
|
|
402
|
+
const addressesExcerpt = [...excerptTerms].some((term) =>
|
|
403
|
+
responseTerms.has(term),
|
|
404
|
+
);
|
|
405
|
+
const answerOnlyConfirmsFormat =
|
|
406
|
+
anotherExcerpt &&
|
|
407
|
+
!addressesExcerpt &&
|
|
408
|
+
(bubbles || []).join("").length <= 48 &&
|
|
409
|
+
/(?:一段|整段|不拆|连贯内容)/u.test((bubbles || []).join(""));
|
|
410
|
+
if (answerOnlyConfirmsFormat)
|
|
411
|
+
issues.push(
|
|
412
|
+
"同一轮还分享了新的作品内容;简短确认分段偏好后,也要在同一条消息里回应那段内容。",
|
|
413
|
+
);
|
|
414
|
+
const asksWhyPriorClaim =
|
|
415
|
+
/(?:为什么|怎么会).{0,12}(?:之前|刚才|上次)?.{0,12}(?:说|回答|否认)|(?:之前|刚才|上次).{0,12}(?:为什么|怎么会).{0,12}(?:说|回答|否认)/u.test(
|
|
416
|
+
latestHumanText,
|
|
417
|
+
);
|
|
418
|
+
if (
|
|
419
|
+
asksWhyPriorClaim &&
|
|
420
|
+
(bubbles || []).some((line) =>
|
|
421
|
+
/(?:因为|原因是|是由于).{0,24}(?:没(?:有)?核对|没(?:有)?检查|忘了|记错|搞混|混淆|没注意|没确认|误读|没读到|没看到记录)/u.test(
|
|
422
|
+
line,
|
|
423
|
+
),
|
|
424
|
+
)
|
|
425
|
+
)
|
|
426
|
+
issues.push(
|
|
427
|
+
"对话没有记下先前错误回答的原因;不能把忘记、没核对或记混猜成事实,直接说不知道当时为什么那样回答。",
|
|
428
|
+
);
|
|
429
|
+
const currentPeople = (snapshot.messages || []).filter(
|
|
430
|
+
(message) =>
|
|
431
|
+
(snapshot.batchIds || []).includes(message.id) &&
|
|
432
|
+
message.role === "user" &&
|
|
433
|
+
!botIds.has(message.id),
|
|
434
|
+
);
|
|
435
|
+
const newComedyRequest = currentPeople.some(
|
|
436
|
+
(message) =>
|
|
437
|
+
/(?:一起写|写个|写一部|新写).{0,12}(?:喜剧|段子|搞笑)/u.test(
|
|
438
|
+
message.text || "",
|
|
439
|
+
) &&
|
|
440
|
+
!/(?:改编|改.{0,6}诗|用.{0,6}诗稿|基于.{0,6}诗)/u.test(
|
|
441
|
+
message.text || "",
|
|
442
|
+
),
|
|
443
|
+
);
|
|
444
|
+
const acknowledgesPoemCorrection = currentPeople.some((message) =>
|
|
445
|
+
/(?:不是|没让|不要).{0,14}(?:改|诗稿|诗)/u.test(message.text || ""),
|
|
446
|
+
);
|
|
447
|
+
const olderPoem = (snapshot.messages || []).some(
|
|
448
|
+
(message) =>
|
|
449
|
+
!(snapshot.batchIds || []).includes(message.id) &&
|
|
450
|
+
message.role === "user" &&
|
|
451
|
+
/诗稿|诗歌|诗篇/u.test(message.text || ""),
|
|
452
|
+
);
|
|
453
|
+
if (
|
|
454
|
+
newComedyRequest &&
|
|
455
|
+
olderPoem &&
|
|
456
|
+
!acknowledgesPoemCorrection &&
|
|
457
|
+
(bubbles || []).some((line) =>
|
|
458
|
+
/凛.{0,12}(?:念诗|读诗|发诗|诗稿)|(?:诗稿|诗歌|诗篇).{0,12}(?:作为|当作|改成|变成|写成|拿来|设定)/u.test(
|
|
459
|
+
line,
|
|
460
|
+
),
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
issues.push(
|
|
464
|
+
"对方提议新写喜剧,旧诗稿只是其他话题;不要擅自把凛或未发布的诗稿写成新作品题材,换成独立虚构场景。",
|
|
465
|
+
);
|
|
131
466
|
for (const line of bubbles || []) {
|
|
132
467
|
const batch = (snapshot.messages || []).filter((m) =>
|
|
133
468
|
(snapshot.batchIds || []).includes(m.id),
|
|
@@ -158,7 +493,25 @@ export function groundingIssues(bubbles, snapshot) {
|
|
|
158
493
|
issues.push(
|
|
159
494
|
"把吃饭、食堂、外卖、出门或实拍说成自己的实际生活,但本轮没有这类实际能力;回应真实能做的事情,不能拿旧的人类生活台词当经历",
|
|
160
495
|
);
|
|
161
|
-
|
|
496
|
+
const hasGameControl = (snapshot.canDo || []).some((item) =>
|
|
497
|
+
/game.*(?:control|client|input)|(?:control|input).*game/i.test(
|
|
498
|
+
item.action || "",
|
|
499
|
+
),
|
|
500
|
+
);
|
|
501
|
+
if (
|
|
502
|
+
!fiction &&
|
|
503
|
+
!hasGameControl &&
|
|
504
|
+
/像\s*(?:《[^》]+》|[a-z][a-z0-9.! ]{1,30})\s*那样[^。!?]{0,10}(?:在客户端|操作客户端|按键)/i.test(
|
|
505
|
+
line,
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
issues.push(
|
|
509
|
+
"把另一部游戏说成自己实际操作过客户端的参照,但当前没有游戏客户端操作能力;真实的剧情阅读和笔记不等于客户端经历,不为解释一处错误再编造另一段经历",
|
|
510
|
+
);
|
|
511
|
+
if (
|
|
512
|
+
!routineSocialReply(line, snapshot) &&
|
|
513
|
+
recent.some((m) => echoesRecentWording(line, m.text))
|
|
514
|
+
) {
|
|
162
515
|
// A requested quote, calculation or factual repeat remains legitimate.
|
|
163
516
|
if (!requestedRepeat(snapshot))
|
|
164
517
|
issues.push(
|
|
@@ -172,7 +525,23 @@ export function groundingIssues(bubbles, snapshot) {
|
|
|
172
525
|
export function fixedReplyHabit(content) {
|
|
173
526
|
const value = String(content || "");
|
|
174
527
|
if (/不再|不要|不该|避免/.test(value)) return false;
|
|
528
|
+
if (
|
|
529
|
+
/(?:只|就|一律|默认)(?:回|说|用|认|给建议)|只问一句|一睁眼先问|先问的是睡眠|被@到才说|简短回应|认完就继续|安静地陪着比给出方案|建议给得具体不空谈/.test(
|
|
530
|
+
value,
|
|
531
|
+
)
|
|
532
|
+
)
|
|
533
|
+
return true;
|
|
175
534
|
return /默认反应|基本定型|(?:每次|一律|必须|固定|就(?:只)?回|只(?:说|回)|直接(?:说|回)|问一句|我会用|习惯用|爱用|常用|时说).{0,32}[「“"『].{1,40}[」”"』]/.test(
|
|
176
535
|
value,
|
|
177
536
|
);
|
|
178
537
|
}
|
|
538
|
+
|
|
539
|
+
// Stored impressions may contain real preferences and a learned reply script
|
|
540
|
+
// in the same paragraph. Keep the former without replaying the latter as law.
|
|
541
|
+
export function conversationalMemory(content) {
|
|
542
|
+
return String(content || "")
|
|
543
|
+
.split(/(?<=[。!?;;])/)
|
|
544
|
+
.filter((part) => !fixedReplyHabit(part))
|
|
545
|
+
.join("")
|
|
546
|
+
.trim();
|
|
547
|
+
}
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
conversationGrounding,
|
|
3
|
+
conversationalMemory,
|
|
4
|
+
socialOnlyBatch,
|
|
5
|
+
recentOwnMessages,
|
|
6
|
+
} from "./conversation-grounding.js";
|
|
7
|
+
import { relevantContext } from "../mind/context-selection.js";
|
|
8
|
+
import { interestTerms } from "../mind/attention.js";
|
|
9
|
+
|
|
1
10
|
// A small, factual tail after the larger memory context. It preserves message
|
|
2
11
|
// ownership and quote chains; it does not guess intent from isolated keywords.
|
|
3
12
|
export function currentExchange(snapshot) {
|
|
@@ -19,9 +28,195 @@ export function currentExchange(snapshot) {
|
|
|
19
28
|
return {
|
|
20
29
|
current,
|
|
21
30
|
quoted: [...quoted.values()],
|
|
22
|
-
recentOwnWords:
|
|
31
|
+
recentOwnWords: recentOwnMessages(snapshot, 3),
|
|
32
|
+
humanMessages: current.filter(
|
|
33
|
+
(m) =>
|
|
34
|
+
m.role === "user" &&
|
|
35
|
+
!conversationGrounding(snapshot).botMessageIds.includes(m.id),
|
|
36
|
+
),
|
|
37
|
+
thirdPartyClaims: current
|
|
38
|
+
.filter(
|
|
39
|
+
(m) =>
|
|
40
|
+
conversationGrounding(snapshot).botMessageIds.includes(m.id) &&
|
|
41
|
+
/(?:他|她|对方|人家)(?:指的是|说的是|意思是|是说)/.test(m.text || ""),
|
|
42
|
+
)
|
|
43
|
+
.map((m) => ({
|
|
44
|
+
id: m.id,
|
|
45
|
+
speaker: m.speaker,
|
|
46
|
+
name: m.name,
|
|
47
|
+
text: m.text,
|
|
48
|
+
status:
|
|
49
|
+
"这是当前说话人对别人的解释;须核对那个人自己的原话,不能视作本人确认",
|
|
50
|
+
})),
|
|
23
51
|
instruction:
|
|
24
|
-
"
|
|
52
|
+
"先读本轮原话、引用和补充,确定谁在对谁说、具体问什么或表达什么。同批有人认真提问或想加入时,别因后面有人刷hh或另一机器人自说自话而漏掉。人和机器人都按实际内容理解,后者的纠正没有裁定别人意思的权力。历史只用于解释这轮,不替换眼前的问题。缩写和省略从同一话题找;多个合理对象就轻问,不把自己的猜测限定成唯二选项。自己的旧话是待核对的记录,不是正确答案或说话模板。",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function dialogueContext(snapshot) {
|
|
57
|
+
const { sourceRows, batch, persona, ...context } = snapshot;
|
|
58
|
+
const inner = { ...context.inner };
|
|
59
|
+
const self = { ...context.self };
|
|
60
|
+
const fresh = new Set(snapshot.batchIds || []);
|
|
61
|
+
const cues = (snapshot.messages || [])
|
|
62
|
+
.filter((m) => fresh.has(m.id) && m.role === "user")
|
|
63
|
+
.map((m) => interestTerms([m.text]));
|
|
64
|
+
const currentWords = (snapshot.messages || [])
|
|
65
|
+
.filter((m) => fresh.has(m.id) && m.role === "user")
|
|
66
|
+
.map((m) => m.text || "")
|
|
67
|
+
.join(" ");
|
|
68
|
+
const recallAsked =
|
|
69
|
+
inner.continuity?.requested ||
|
|
70
|
+
/刚才|之前|上次|记得|说过|答应|约定|那件|那本|那篇|进度|玩到|读到|写到|看过|读过|写过|在推|明明.{0,5}(?:玩|写|读)|(?:为什么|怎么).{0,12}(?:说|叫|喊)/.test(
|
|
71
|
+
currentWords,
|
|
72
|
+
);
|
|
73
|
+
if (inner.continuity && !recallAsked) {
|
|
74
|
+
inner.continuity = {
|
|
75
|
+
...inner.continuity,
|
|
76
|
+
people: (inner.continuity.people || []).map((person) => {
|
|
77
|
+
const selected = { ...person };
|
|
78
|
+
for (const key of [
|
|
79
|
+
"recentShared",
|
|
80
|
+
"myPrivateIntentions",
|
|
81
|
+
"myElsewhereWords",
|
|
82
|
+
"sharedMoments",
|
|
83
|
+
"memories",
|
|
84
|
+
]) {
|
|
85
|
+
if (!Array.isArray(person[key])) continue;
|
|
86
|
+
selected[key] = relevantContext(
|
|
87
|
+
person[key].map((row) => ({
|
|
88
|
+
row,
|
|
89
|
+
content:
|
|
90
|
+
typeof row === "string"
|
|
91
|
+
? row
|
|
92
|
+
: row.text || row.content || JSON.stringify(row),
|
|
93
|
+
})),
|
|
94
|
+
{
|
|
95
|
+
cues,
|
|
96
|
+
limit: 3,
|
|
97
|
+
requireOverlap: true,
|
|
98
|
+
pinned: (item) =>
|
|
99
|
+
/想你|喜欢你|爱你/.test(currentWords) &&
|
|
100
|
+
/想你|喜欢你|爱你/.test(item.content) &&
|
|
101
|
+
!/推游戏|写完|读完|进度|汇报|交稿|发稿|答应.{0,20}(?:玩|读|写)|承诺.{0,20}(?:玩|读|写)/.test(
|
|
102
|
+
item.content,
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
).map((item) => item.row);
|
|
106
|
+
}
|
|
107
|
+
return selected;
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const messages = snapshot.messages || [];
|
|
112
|
+
const mustKeep = new Set(
|
|
113
|
+
[
|
|
114
|
+
...messages.slice(-40),
|
|
115
|
+
...currentExchange(snapshot).quoted,
|
|
116
|
+
...messages.filter((m) => fresh.has(m.id)),
|
|
117
|
+
].map((m) => m.id),
|
|
118
|
+
);
|
|
119
|
+
const older = relevantContext(
|
|
120
|
+
messages
|
|
121
|
+
.filter((m) => !mustKeep.has(m.id))
|
|
122
|
+
.map((m) => ({ ...m, content: m.text })),
|
|
123
|
+
{ cues, limit: 10, requireOverlap: true },
|
|
124
|
+
);
|
|
125
|
+
for (const row of older) mustKeep.add(row.id);
|
|
126
|
+
context.messages = messages.filter((m) => mustKeep.has(m.id));
|
|
127
|
+
// Historical compression is still accessible, selected by the current
|
|
128
|
+
// topic. Unrelated weeks of old appraisals must not dominate an ordinary turn.
|
|
129
|
+
for (const key of ["summaries", "stages"]) {
|
|
130
|
+
if (Array.isArray(context[key]))
|
|
131
|
+
context[key] = relevantContext(
|
|
132
|
+
context[key].map((row) => ({ ...row, content: row.summary })),
|
|
133
|
+
{
|
|
134
|
+
cues,
|
|
135
|
+
limit: 2,
|
|
136
|
+
requireOverlap: true,
|
|
137
|
+
pinned: (row) =>
|
|
138
|
+
recallAsked && row.summary === context[key].at(-1)?.summary,
|
|
139
|
+
},
|
|
140
|
+
).map(({ content, ...row }) => row);
|
|
141
|
+
}
|
|
142
|
+
context.addressConventions = (snapshot.summaries || [])
|
|
143
|
+
.flatMap((row) =>
|
|
144
|
+
String(row.summary || "")
|
|
145
|
+
.split(/[。;\n]/)
|
|
146
|
+
.filter((clause) =>
|
|
147
|
+
/称呼约定|互称|私(?:下|聊).{0,12}(?:称|叫|喊)|(?:称|叫|喊).{0,12}(?:仅限|只限|私下|私聊)|宝宝.{0,8}(?:仅限|只叫|只称|只喊)|(?:希望|要求|不喜欢|别用|不要用|少用).{0,25}(?:称呼|叫|喊|开头|口吻|单字|语气)/.test(
|
|
148
|
+
clause,
|
|
149
|
+
),
|
|
150
|
+
)
|
|
151
|
+
.map((content) => ({ period: row.period, content: content.trim() })),
|
|
152
|
+
)
|
|
153
|
+
.slice(-4);
|
|
154
|
+
if (socialOnlyBatch(snapshot)) {
|
|
155
|
+
const currentTimes = messages
|
|
156
|
+
.filter((m) => fresh.has(m.id))
|
|
157
|
+
.map((m) => m.time)
|
|
158
|
+
.filter(Number.isFinite);
|
|
159
|
+
const now = currentTimes.length ? Math.max(...currentTimes) : null;
|
|
160
|
+
const quoted = new Set(currentExchange(snapshot).quoted.map((m) => m.id));
|
|
161
|
+
context.messages = context.messages.filter(
|
|
162
|
+
(m) =>
|
|
163
|
+
fresh.has(m.id) ||
|
|
164
|
+
quoted.has(m.id) ||
|
|
165
|
+
(Number.isFinite(now) &&
|
|
166
|
+
Number.isFinite(m.time) &&
|
|
167
|
+
now - m.time <= 30 * 60000),
|
|
168
|
+
);
|
|
169
|
+
context.summaries = [];
|
|
170
|
+
context.stages = [];
|
|
171
|
+
context.memories = [];
|
|
172
|
+
delete context.recalled;
|
|
173
|
+
delete context.topics;
|
|
174
|
+
delete inner.onMind;
|
|
175
|
+
delete inner.relatedSelf;
|
|
176
|
+
delete inner.with;
|
|
177
|
+
delete inner.expecting;
|
|
178
|
+
if (inner.state) inner.state = inner.state.replace(/([^)]*)/g, "");
|
|
179
|
+
if (inner.currentLife)
|
|
180
|
+
inner.currentLife = { current: inner.currentLife.current };
|
|
181
|
+
}
|
|
182
|
+
if (self.here) {
|
|
183
|
+
const recalled = relevantContext(
|
|
184
|
+
[{ content: conversationalMemory(self.here) }],
|
|
185
|
+
{ cues, limit: 1, requireOverlap: true },
|
|
186
|
+
);
|
|
187
|
+
if (recalled.length)
|
|
188
|
+
inner.placeMemory = {
|
|
189
|
+
content: recalled[0].content,
|
|
190
|
+
status: "过去对这个地方的印象,不是人格定义或回复规则,可以有误",
|
|
191
|
+
};
|
|
192
|
+
delete self.here;
|
|
193
|
+
}
|
|
194
|
+
// Planning reasons and prior appraisal reports are useful to the life
|
|
195
|
+
// planner, but repeatedly priming ordinary speech with them creates scripts.
|
|
196
|
+
delete inner.stood;
|
|
197
|
+
delete inner.will;
|
|
198
|
+
if (inner.currentLife) {
|
|
199
|
+
const life = inner.currentLife;
|
|
200
|
+
const compact = (row) => ({
|
|
201
|
+
id: row.id,
|
|
202
|
+
title: row.title,
|
|
203
|
+
state: row.state,
|
|
204
|
+
...(row.wait ? { wait: row.wait } : {}),
|
|
205
|
+
...(row.plannedFor ? { plannedFor: row.plannedFor } : {}),
|
|
206
|
+
});
|
|
207
|
+
inner.currentLife = {
|
|
208
|
+
...life,
|
|
209
|
+
pending: (life.pending || []).map(compact),
|
|
210
|
+
intentions: (life.intentions || []).map(compact),
|
|
211
|
+
worksCoverage:
|
|
212
|
+
"以下是按本轮话题选出的作品片段,不是全部历史;缺少一篇不能证明从未写过或读过。",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
...context,
|
|
217
|
+
self,
|
|
218
|
+
inner,
|
|
219
|
+
grounding: conversationGrounding(snapshot),
|
|
25
220
|
};
|
|
26
221
|
}
|
|
27
222
|
|