luckytri 1.0.7 → 1.0.8
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 +10 -0
- package/docs/zh/CHANGELOG.md +10 -0
- package/package.json +1 -1
- package/public/guide.en.html +3 -3
- package/public/guide.html +3 -3
- package/server/core/conversation-cues.js +29 -9
- package/server/core/conversation-grounding.js +130 -4
- package/server/core/dialogue-context.js +194 -2
- package/server/core/dialogue-contract.js +35 -0
- package/server/core/initiative-context.js +4 -2
- package/server/core/model-manager.js +5 -8
- package/server/core/persona-manager.js +44 -9
- package/server/core/reader-check.js +62 -0
- package/server/core/reply-delivery.js +68 -3
- package/server/core/response-generator.js +43 -9
- package/server/core/response-validator.js +24 -6
- package/server/core/turn.js +43 -10
- package/server/i18n/errors.js +2 -0
- package/server/i18n/labels.js +6 -5
- package/server/mind/expression-grounding.js +21 -0
- package/server/mind/own-voice.js +35 -0
- package/server/mind/reading.js +15 -3
- 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 +6 -2
- 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 +41 -3
- package/server/mind/time/works.js +104 -15
- package/server/mind/view.js +20 -7
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p align="center"><strong>Let the days she lives count.</strong></p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<img alt="version" src="https://img.shields.io/
|
|
10
|
+
<a href="https://www.npmjs.com/package/luckytri"><img alt="npm version" src="https://img.shields.io/npm/v/luckytri?color=ff8fb1"></a>
|
|
11
11
|
<img alt="license" src="https://img.shields.io/badge/license-MIT-7fd6c2">
|
|
12
12
|
<img alt="node" src="https://img.shields.io/badge/node-%E2%89%A5%2024.5-8fb8ff">
|
|
13
13
|
<img alt="channels" src="https://img.shields.io/badge/QQ-OneBot%2011%20%7C%20official%20bot-ffd37f">
|
|
@@ -92,10 +92,14 @@ The full design is in [Her life](docs/en/her-life.md) and [Architecture and exte
|
|
|
92
92
|
|
|
93
93
|
## What she can do
|
|
94
94
|
|
|
95
|
+
Recent work focuses on continuity and clearer conversation. Account-based recognition links public encounters across groups and private chats. Relationships, forms of address, her own views and private promises retain their sources and visibility boundaries. An ordinary greeting need not bring back unfinished tasks or demonstrate how much she remembers.
|
|
96
|
+
|
|
97
|
+
**Conversation fixes in 1.0.8:** Attend to the current speaker, follow-up messages and quotations; explain plainly when someone cannot follow. Another bot's interpretation does not replace the person's confirmation, and a conversation can pause when there is nothing new to add. Reading, writing and game references require the appropriate material: waiting for a manuscript does not become a writing project, and reading notes are not game-client operation. See the [changelog](docs/en/CHANGELOG.md). These changes still need testing in everyday interaction; automated scores are not a guarantee of conversation quality.
|
|
98
|
+
|
|
95
99
|
| Area | What it does today |
|
|
96
100
|
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
97
101
|
| Time and solitude | Lives a day on her own rhythm; in quiet hours sorts her thoughts, reads, writes a diary; reviews every so often, rewrites her story and turns to a new chapter |
|
|
98
|
-
| Doing her own things | Carries out
|
|
102
|
+
| Doing her own things | Carries out her reading, writing and thinking plans; game activities primarily read and record reference material; works are saved under Lifetime and Time |
|
|
99
103
|
| Self and mood | Forms opinions, tastes, concerns and wishes from sourced experiences; changes can be looked back on and undone |
|
|
100
104
|
| Relationships and memory | One memory across groups and private chats; tells "public / private / confidential" apart; private things never surface in another room |
|
|
101
105
|
| Attention and choice | Understands multi-person talk, @-mentions and quotes, and decides to read closely, to speak or to stay silent; can also reach out from her own thoughts |
|
|
@@ -202,7 +206,7 @@ To continue an existing source instance, stop it first, then use `luckytri --hom
|
|
|
202
206
|
| `EMBEDDING_API_KEY` | Optional; a separate key for the knowledge embedding service |
|
|
203
207
|
| `BACKUP_INTERVAL_HOURS` / `BACKUP_KEEP` | Automatic backup interval (24 hours by default, `0` turns it off) and how many to keep (7 by default) |
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
The database, chat records, model keys and backups live locally in the ignored `data/` directory and are excluded from the repository and npm package. When you use a remote model, the context needed for a response is sent to your configured provider. While running, the server makes one integrity-checked backup a day; backups share a disk with the database, so copy important ones elsewhere. Never commit `.env`, the database or logs. See [Security](.github/SECURITY.md) and [Backup and recovery](docs/en/recovery.md).
|
|
206
210
|
|
|
207
211
|
## Documentation
|
|
208
212
|
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p align="center"><strong>让她走过的日子算数。</strong></p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<img alt="version" src="https://img.shields.io/
|
|
10
|
+
<a href="https://www.npmjs.com/package/luckytri"><img alt="npm version" src="https://img.shields.io/npm/v/luckytri?color=ff8fb1"></a>
|
|
11
11
|
<img alt="license" src="https://img.shields.io/badge/license-MIT-7fd6c2">
|
|
12
12
|
<img alt="node" src="https://img.shields.io/badge/node-%E2%89%A5%2024.5-8fb8ff">
|
|
13
13
|
<img alt="channels" src="https://img.shields.io/badge/QQ-OneBot%2011%20%7C%20%E5%AE%98%E6%96%B9%E6%9C%BA%E5%99%A8%E4%BA%BA-ffd37f">
|
|
@@ -92,10 +92,14 @@ flowchart LR
|
|
|
92
92
|
|
|
93
93
|
## 能力一览
|
|
94
94
|
|
|
95
|
+
近期更新集中在让相处更连贯、说话更清楚。她按账号认出同一个人,在不同群与私聊间衔接公开经历;关系、称呼、自己的看法和私人约定分别保留来源与可见范围。普通问候不需要带出旧任务,每次开口也不需要证明自己记住了多少事。
|
|
96
|
+
|
|
97
|
+
**1.0.8 的对话修复:** 优先理解当前说话人的问题、连续补充与引用;对方说没听懂,就换成能直接理解的话。其他机器人的猜测不代替本人确认;没有新内容时可以停下来。阅读、写作与游戏资料各自核对真实材料,等待来稿不会被执行成创作,也不会把阅读笔记说成操作游戏客户端。完整变化见 [版本记录](docs/zh/CHANGELOG.md)。这些修复仍需真实相处检验,项目不把自动评分当作回复质量的保证。
|
|
98
|
+
|
|
95
99
|
| 方面 | 现在能做什么 |
|
|
96
100
|
| ------------ | --------------------------------------------------------------------------------------------------------- |
|
|
97
101
|
| 时间与独处 | 按自己的作息过一天;安静时整理想法、阅读资料、写日记;每隔一段时间回顾,改写自传、翻开新的一章 |
|
|
98
|
-
| 做自己的事 |
|
|
102
|
+
| 做自己的事 | 执行自己的阅读、写作与思考计划;游戏内容以参考资料阅读与记录为主;作品保存在「一生」「时间」中 |
|
|
99
103
|
| 自我与心境 | 从有来源的经历里形成看法、喜好、关注和想做的事;变化可以回看和撤销 |
|
|
100
104
|
| 关系与记忆 | 一份跨群、跨私聊的记忆;区分「公开 / 私下 / 保密」;私下的事不会出现在别的房间 |
|
|
101
105
|
| 注意力与选择 | 理解多人对话、@ 与引用关系,决定细看、开口或沉默;也能从自己的念头出发主动联系 |
|
|
@@ -202,7 +206,7 @@ npm start
|
|
|
202
206
|
| `EMBEDDING_API_KEY` | 可选;独立的知识向量服务密钥 |
|
|
203
207
|
| `BACKUP_INTERVAL_HOURS` / `BACKUP_KEEP` | 自动备份间隔(默认 24 小时,`0` 关闭)与保留份数(默认 7) |
|
|
204
208
|
|
|
205
|
-
|
|
209
|
+
数据库、聊天记录、模型密钥和备份保存在本机被忽略的 `data/` 目录,不随源码或 npm 包发布。使用远程模型时,回复所需的上下文会发送给你配置的模型服务。服务运行时每天自动做一份经过完整性检查的备份;备份和数据库在同一块磁盘上,重要的请自己复制到别处。不要提交 `.env`、数据库或日志。详见 [安全说明](.github/SECURITY.md) 与 [数据备份与恢复](docs/zh/recovery.md)。
|
|
206
210
|
|
|
207
211
|
## 文档
|
|
208
212
|
|
package/docs/en/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<p align="right"><a href="../zh/CHANGELOG.md">中文</a> · <b>English</b></p>
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
|
+
|
|
5
|
+
## 1.0.8 — 2026-10-10
|
|
6
|
+
|
|
7
|
+
- Rework dialogue instructions and context around the current question, proposal or feeling. Old activity reports, room shorthand and self-summaries no longer dominate ordinary exchanges. Greeting images retain their immediate context and agreed forms of address remain available.
|
|
8
|
+
- Allow natural recurring affection and greetings without forcing invented counts, elapsed-time reports or lifestyle advice. Complete reactions are no longer mechanically rewritten to fit a twenty-character limit.
|
|
9
|
+
- Preserve the actual addressee and quoted evidence in group conversations. Later laughter does not erase a serious question, and another bot's interpretation is not the person's confirmation. Empty bot reports do not create confirmation chains or relationship growth.
|
|
10
|
+
- Add an independent comprehensibility check for explanation requests; revisions and factual review retain the current question. Validation respects the configured reasoning effort.
|
|
11
|
+
- Recall relevant historical work versions without denying earlier reading because recent records are incomplete or revealing unrelated private text. Exclude unrelated web material from game progress and sharing while retaining the archive.
|
|
12
|
+
- Distinguish someone else's writing from her own reading promise. Waiting for a manuscript does not execute as writing or substitute another or older book. New self-expressions check actions and participants rather than turning wishes into completed experiences.
|
|
13
|
+
- Update both READMEs, capability boundaries and the release badge; expand historical replay, sequential group scenarios and regression coverage. Transcripts and model reports remain local.
|
|
4
14
|
|
|
5
15
|
## 1.0.7 — 2026-10-09
|
|
6
16
|
|
package/docs/zh/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
<p align="right"><b>中文</b> · <a href="../en/CHANGELOG.md">English</a></p>
|
|
2
2
|
|
|
3
3
|
# 版本记录
|
|
4
|
+
|
|
5
|
+
## 1.0.8 — 2026-10-10
|
|
6
|
+
|
|
7
|
+
- 重新整理对话提示与上下文:优先回答眼前的具体问题、提议和感受,不再让旧活动汇报、群内暗号和过去的自我总结主导普通交流。问候附图同样按当前语境处理,约定称呼仍然保留。
|
|
8
|
+
- 修复普通亲近表达被重复检查退回的问题;“我也想你”、晚安等自然回应可以再次使用,无须编出次数、时间间隔或生活建议。完整的一句反应不再因二十字限制被机械改写。
|
|
9
|
+
- 多人对话保留实际回应对象与引用证据,不因后面的笑声漏掉认真提问,也不把另一机器人的猜测当本人确认。没有新内容的机器人报备不再形成确认链或关系增长。
|
|
10
|
+
- 增加独立的可理解性检查:对方要求解释时,答案须让提问者读懂;修订与事实复核保留当前问题,校验阶段遵循配置的推理程度。
|
|
11
|
+
- 作品召回搜索相关历史版本;已有阅读可以核实,不再因近期记录缺失否认过去,也不披露无关私下正文。排除混入游戏资料的无关网页,保留原档案并阻止其成为进度证据或分享内容。
|
|
12
|
+
- 区分别人的写作与自己的阅读约定;等待正文时不执行成写作,不拿无关或旧书替代。新保存的念头核对行动与参与者,避免把愿望写成已完成经历。
|
|
13
|
+
- 更新中英文 README、能力边界与发布版本徽章;扩展历史回放、连续多人对话和回归测试。聊天记录与模型评测报告只留本地。
|
|
4
14
|
|
|
5
15
|
## 1.0.7 — 2026-10-09
|
|
6
16
|
|
package/package.json
CHANGED
package/public/guide.en.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
-
<title>LuckyTri v1.0.
|
|
6
|
+
<title>LuckyTri v1.0.8 · Getting Started</title>
|
|
7
7
|
<style>
|
|
8
8
|
* {
|
|
9
9
|
box-sizing: border-box;
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
<body>
|
|
270
270
|
<header>
|
|
271
271
|
<div>
|
|
272
|
-
<b>LuckyTri · Handbook</b><small>TIME · MEMORY · CHOICE / v1.0.
|
|
272
|
+
<b>LuckyTri · Handbook</b><small>TIME · MEMORY · CHOICE / v1.0.8</small>
|
|
273
273
|
</div>
|
|
274
274
|
<nav>
|
|
275
275
|
<a href="/#setup">Back to the studio</a
|
|
@@ -1057,7 +1057,7 @@ npm start
|
|
|
1057
1057
|
</div>
|
|
1058
1058
|
<footer>
|
|
1059
1059
|
LuckyTri · so that experience and choice stay coherent. This guide matches
|
|
1060
|
-
the v1.0.
|
|
1060
|
+
the v1.0.8 interface.
|
|
1061
1061
|
</footer>
|
|
1062
1062
|
</body>
|
|
1063
1063
|
</html>
|
package/public/guide.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
6
|
-
<title>LuckyTri v1.0.
|
|
6
|
+
<title>LuckyTri v1.0.8 · 使用教程</title>
|
|
7
7
|
<style>
|
|
8
8
|
* {
|
|
9
9
|
box-sizing: border-box;
|
|
@@ -269,7 +269,7 @@
|
|
|
269
269
|
<body>
|
|
270
270
|
<header>
|
|
271
271
|
<div>
|
|
272
|
-
<b>LuckyTri · 上手手册</b><small>TIME · MEMORY · CHOICE / v1.0.
|
|
272
|
+
<b>LuckyTri · 上手手册</b><small>TIME · MEMORY · CHOICE / v1.0.8</small>
|
|
273
273
|
</div>
|
|
274
274
|
<nav>
|
|
275
275
|
<a href="/#setup">返回管理台</a
|
|
@@ -864,6 +864,6 @@ npm start
|
|
|
864
864
|
</p>
|
|
865
865
|
</article>
|
|
866
866
|
</div>
|
|
867
|
-
<footer>LuckyTri · 让经历和选择彼此连贯。教程与 v1.0.
|
|
867
|
+
<footer>LuckyTri · 让经历和选择彼此连贯。教程与 v1.0.8 界面同步。</footer>
|
|
868
868
|
</body>
|
|
869
869
|
</html>
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { claimedPlay } from "../mind/guard.js";
|
|
2
|
+
import { readerQuestions } from "./reader-check.js";
|
|
3
|
+
import {
|
|
4
|
+
recentOwnMessages,
|
|
5
|
+
routineSocialReply,
|
|
6
|
+
} from "./conversation-grounding.js";
|
|
2
7
|
|
|
3
8
|
// Stable message timestamps stay with history; changing clock/style cues belong at the tail.
|
|
4
9
|
const clockFormats = new Map();
|
|
@@ -73,7 +78,11 @@ export function replyFocus(snapshot, decision) {
|
|
|
73
78
|
if (
|
|
74
79
|
/看不懂|没看懂|听不懂|太抽象|说人话|复读|别重复|好好说话|你在说什么|你在讲什么/.test(
|
|
75
80
|
latest,
|
|
76
|
-
)
|
|
81
|
+
) ||
|
|
82
|
+
(/^[??]+$/.test(latest) &&
|
|
83
|
+
readerQuestions(snapshot, decision).some((q) =>
|
|
84
|
+
targets.some((m) => m.id === q.id),
|
|
85
|
+
))
|
|
77
86
|
)
|
|
78
87
|
return {
|
|
79
88
|
kind: "readability_repair",
|
|
@@ -185,7 +194,7 @@ export function conversationCues(
|
|
|
185
194
|
now = Date.now(),
|
|
186
195
|
timeZone,
|
|
187
196
|
) {
|
|
188
|
-
const recent =
|
|
197
|
+
const recent = recentOwnMessages({ messages, batchIds }, 8);
|
|
189
198
|
const batch = messages.filter((m) => batchIds.includes(m.id));
|
|
190
199
|
const previous = messages
|
|
191
200
|
.filter(
|
|
@@ -207,7 +216,7 @@ export function conversationCues(
|
|
|
207
216
|
ellipsisCount: recent.filter((m) => ellipsis.test(m.text)).length,
|
|
208
217
|
recentReplies: recent.map((m) => ({ id: m.id, text: m.text })),
|
|
209
218
|
instruction:
|
|
210
|
-
"
|
|
219
|
+
"这是近期说过的话,不是风格示范。不继承模板或无意义第二句;新问候和亲近表达可以自然回应,晚安、我也想你、hh不必为避免重复而编花样。没有新意思的承诺和确认不用换词再发。",
|
|
211
220
|
},
|
|
212
221
|
};
|
|
213
222
|
}
|
|
@@ -218,10 +227,7 @@ export function conversationalIssues(result, snapshot, decision = {}) {
|
|
|
218
227
|
: (snapshot.messages || []).filter((m) =>
|
|
219
228
|
snapshot.batchIds?.includes(m.id),
|
|
220
229
|
);
|
|
221
|
-
const recent = snapshot.
|
|
222
|
-
.filter((m) => m.role === "assistant")
|
|
223
|
-
.slice(-8)
|
|
224
|
-
.map((m) => m.text);
|
|
230
|
+
const recent = recentOwnMessages(snapshot, 8).map((m) => m.text);
|
|
225
231
|
const issues = [];
|
|
226
232
|
if (
|
|
227
233
|
replyFocus(snapshot, decision).kind === "readability_repair" &&
|
|
@@ -327,9 +333,22 @@ export function conversationalIssues(result, snapshot, decision = {}) {
|
|
|
327
333
|
)
|
|
328
334
|
)
|
|
329
335
|
issues.push("不要为共情虚构自己同样上班或亲历;回应对方的事情即可");
|
|
330
|
-
if (
|
|
336
|
+
if (
|
|
337
|
+
texts.some(
|
|
338
|
+
(line) =>
|
|
339
|
+
claimedPlay(line) &&
|
|
340
|
+
!(
|
|
341
|
+
/通关后|通关才能|解锁|需要.{0,8}通关|二周目.{0,6}(?:设计|设定|模式)/.test(
|
|
342
|
+
line,
|
|
343
|
+
) &&
|
|
344
|
+
!/我.{0,10}(?:已经|之前|刚|早就|确实|全)?(?:玩过|通关|打完|拿到|达成)/.test(
|
|
345
|
+
line,
|
|
346
|
+
)
|
|
347
|
+
),
|
|
348
|
+
)
|
|
349
|
+
)
|
|
331
350
|
issues.push(
|
|
332
|
-
"
|
|
351
|
+
"不要把游戏资料阅读说成实际操控客户端、通关或拿到成就。有真实阅读和笔记时,直接谈读到的内容、进度和感受,不能反过来否认读过或写过;只想了解的作品才说想了解,不用旧的口头夸大当经历证据",
|
|
333
352
|
);
|
|
334
353
|
if (
|
|
335
354
|
texts.some((t) =>
|
|
@@ -355,6 +374,7 @@ export function conversationalIssues(result, snapshot, decision = {}) {
|
|
|
355
374
|
const t = clean(text);
|
|
356
375
|
if (
|
|
357
376
|
t.length >= 8 &&
|
|
377
|
+
!routineSocialReply(text, snapshot) &&
|
|
358
378
|
recent.filter((r) => clean(r).startsWith(t.slice(0, 5))).length >= 2
|
|
359
379
|
)
|
|
360
380
|
issues.push(
|
|
@@ -12,10 +12,86 @@ export const ATTRIBUTION_RULE =
|
|
|
12
12
|
export const EXTERNAL_FACT_RULE =
|
|
13
13
|
"【外部事实与推测】不了解某个软件、模型或别人项目的具体实现时,不把常见的技术路线说成它实际采用的路线。知道一般原理就说明一般原理,同时讲清自己不了解它的细节;‘好像’和‘应该’不能替无依据的细节作证。作品里的人物、辈分、章节也一样:只确定名字就只答名字,不用一串不确定的设定充实答案,更不拿‘隔太久没重看’编造自己的经历。自己的后台记录只证明自己的能力,不证明其他系统怎么运行。appraisal与reason也写这轮具体理解和选择,不用隐喻或猜别人隐藏的动机。";
|
|
14
14
|
|
|
15
|
+
export const READABILITY_REQUEST =
|
|
16
|
+
/看不懂|听不懂|没听懂|没看懂|没懂|听得懂|看得懂|说人话|正常话|太抽象|好好说话|照顾.{0,3}我/;
|
|
17
|
+
|
|
15
18
|
const normalized = (value) =>
|
|
16
19
|
String(value || "")
|
|
17
20
|
.toLowerCase()
|
|
18
21
|
.replace(/[\p{P}\p{Z}\s]/gu, "");
|
|
22
|
+
|
|
23
|
+
export function recentOwnMessages(snapshot, limit = 8) {
|
|
24
|
+
const messages = snapshot.messages || [];
|
|
25
|
+
const batch = new Set(snapshot.batchIds || []);
|
|
26
|
+
const currentTimes = messages
|
|
27
|
+
.filter((m) => batch.has(m.id))
|
|
28
|
+
.map((m) => m.time)
|
|
29
|
+
.filter(Number.isFinite);
|
|
30
|
+
const now = currentTimes.length
|
|
31
|
+
? Math.max(...currentTimes)
|
|
32
|
+
: messages.at(-1)?.time;
|
|
33
|
+
return messages
|
|
34
|
+
.filter(
|
|
35
|
+
(m) =>
|
|
36
|
+
m.role === "assistant" &&
|
|
37
|
+
(!Number.isFinite(now) ||
|
|
38
|
+
!Number.isFinite(m.time) ||
|
|
39
|
+
now - m.time <= 30 * 60000),
|
|
40
|
+
)
|
|
41
|
+
.slice(-limit);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function routineSocialReply(value, snapshot) {
|
|
45
|
+
const words = normalized(value).replace(/\p{S}/gu, "");
|
|
46
|
+
if (words.length > 24) return false;
|
|
47
|
+
const incoming = (snapshot.messages || [])
|
|
48
|
+
.filter(
|
|
49
|
+
(m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
|
|
50
|
+
)
|
|
51
|
+
.map((m) => m.text || "")
|
|
52
|
+
.join(" ");
|
|
53
|
+
return (
|
|
54
|
+
(/在吗|在不在|还在吗/.test(incoming) &&
|
|
55
|
+
/^(?:嗯)?(?:我)?在(?:的|呢|呀|啊|这儿|这里)?$/.test(words)) ||
|
|
56
|
+
(/想你|喜欢你|爱你|抱抱/.test(incoming) &&
|
|
57
|
+
/^(?:宝宝)?(?:(?:我也)?(?:想你|喜欢你|爱你|抱抱)|我也是)(?:了|啊|呀|呢|宝宝)*$/.test(
|
|
58
|
+
words,
|
|
59
|
+
)) ||
|
|
60
|
+
(/晚安|早安|早上好|你好|再见|拜拜/.test(incoming) &&
|
|
61
|
+
/^(?:好|嗯|那)?(?:晚安|早安|早上好|你好|再见|拜拜)(?:喵|呀|啊|啦|宝宝|晚安|早安)*$/.test(
|
|
62
|
+
words,
|
|
63
|
+
))
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function socialOnlyBatch(snapshot) {
|
|
68
|
+
const current = (snapshot.messages || []).filter(
|
|
69
|
+
(m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
|
|
70
|
+
);
|
|
71
|
+
const name = normalized(snapshot.persona?.name || "");
|
|
72
|
+
const words = current.map((m) => {
|
|
73
|
+
let value = normalized(
|
|
74
|
+
String(m.text || "").replace(/^\[(?:图片|表情|媒体)\]$/, ""),
|
|
75
|
+
).replace(/\p{S}/gu, "");
|
|
76
|
+
if (name) value = value.replaceAll(name, "");
|
|
77
|
+
return value.replaceAll("提及成员", "").replaceAll("@我", "");
|
|
78
|
+
});
|
|
79
|
+
return (
|
|
80
|
+
words.length > 0 &&
|
|
81
|
+
words.some((value) =>
|
|
82
|
+
/想你|爱你|喜欢你|晚安|早安|早上好|你好|再见|拜拜|抱抱|在吗|在不在/.test(
|
|
83
|
+
value,
|
|
84
|
+
),
|
|
85
|
+
) &&
|
|
86
|
+
words.every(
|
|
87
|
+
(value) =>
|
|
88
|
+
value.replace(
|
|
89
|
+
/想你|爱你|喜欢你|晚安|早安|早上好|你好|再见|拜拜|抱抱|在吗|在不在|宝宝|有点|突然|我|也|好|很|真|的|了|呀|啊|呢|喵|嗯/g,
|
|
90
|
+
"",
|
|
91
|
+
) === "",
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
19
95
|
const acknowledgement =
|
|
20
96
|
/^(?:嗯+|哦+|好(?:的|吧)?|行|对(?:呀|啊)?|是的|知道了|收到|晚安|h{2,6}|哈{1,6})$/i;
|
|
21
97
|
const request =
|
|
@@ -124,10 +200,26 @@ export function requestedRepeat(snapshot) {
|
|
|
124
200
|
}
|
|
125
201
|
|
|
126
202
|
export function groundingIssues(bubbles, snapshot) {
|
|
127
|
-
const recent = (snapshot
|
|
128
|
-
.filter((m) => m.role === "assistant")
|
|
129
|
-
.slice(-6);
|
|
203
|
+
const recent = recentOwnMessages(snapshot, 6);
|
|
130
204
|
const issues = [];
|
|
205
|
+
const humanRepair = (snapshot.messages || []).some(
|
|
206
|
+
(m) =>
|
|
207
|
+
(snapshot.batchIds || []).includes(m.id) &&
|
|
208
|
+
m.role === "user" &&
|
|
209
|
+
!conversationGrounding(snapshot).botMessageIds.includes(m.id) &&
|
|
210
|
+
READABILITY_REQUEST.test(m.text || ""),
|
|
211
|
+
);
|
|
212
|
+
if (
|
|
213
|
+
humanRepair &&
|
|
214
|
+
(bubbles || []).some((line) =>
|
|
215
|
+
/你(?:们)?(?:不必|不用|不需要|没义务).{0,5}(?:看懂|听懂|理解)|(?:绕晕|看不懂|听不懂).{0,8}(?:跳过|别管)/.test(
|
|
216
|
+
line,
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
issues.push(
|
|
221
|
+
"对方正想听懂并参与,却让他不用懂或跳过;解释实际事情,自己也不知道的部分就说不知道,不能把他排除在交流之外",
|
|
222
|
+
);
|
|
131
223
|
for (const line of bubbles || []) {
|
|
132
224
|
const batch = (snapshot.messages || []).filter((m) =>
|
|
133
225
|
(snapshot.batchIds || []).includes(m.id),
|
|
@@ -158,7 +250,25 @@ export function groundingIssues(bubbles, snapshot) {
|
|
|
158
250
|
issues.push(
|
|
159
251
|
"把吃饭、食堂、外卖、出门或实拍说成自己的实际生活,但本轮没有这类实际能力;回应真实能做的事情,不能拿旧的人类生活台词当经历",
|
|
160
252
|
);
|
|
161
|
-
|
|
253
|
+
const hasGameControl = (snapshot.canDo || []).some((item) =>
|
|
254
|
+
/game.*(?:control|client|input)|(?:control|input).*game/i.test(
|
|
255
|
+
item.action || "",
|
|
256
|
+
),
|
|
257
|
+
);
|
|
258
|
+
if (
|
|
259
|
+
!fiction &&
|
|
260
|
+
!hasGameControl &&
|
|
261
|
+
/像\s*(?:《[^》]+》|[a-z][a-z0-9.! ]{1,30})\s*那样[^。!?]{0,10}(?:在客户端|操作客户端|按键)/i.test(
|
|
262
|
+
line,
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
issues.push(
|
|
266
|
+
"把另一部游戏说成自己实际操作过客户端的参照,但当前没有游戏客户端操作能力;真实的剧情阅读和笔记不等于客户端经历,不为解释一处错误再编造另一段经历",
|
|
267
|
+
);
|
|
268
|
+
if (
|
|
269
|
+
!routineSocialReply(line, snapshot) &&
|
|
270
|
+
recent.some((m) => echoesRecentWording(line, m.text))
|
|
271
|
+
) {
|
|
162
272
|
// A requested quote, calculation or factual repeat remains legitimate.
|
|
163
273
|
if (!requestedRepeat(snapshot))
|
|
164
274
|
issues.push(
|
|
@@ -172,7 +282,23 @@ export function groundingIssues(bubbles, snapshot) {
|
|
|
172
282
|
export function fixedReplyHabit(content) {
|
|
173
283
|
const value = String(content || "");
|
|
174
284
|
if (/不再|不要|不该|避免/.test(value)) return false;
|
|
285
|
+
if (
|
|
286
|
+
/(?:只|就|一律|默认)(?:回|说|用|认|给建议)|只问一句|一睁眼先问|先问的是睡眠|被@到才说|简短回应|认完就继续|安静地陪着比给出方案|建议给得具体不空谈/.test(
|
|
287
|
+
value,
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
return true;
|
|
175
291
|
return /默认反应|基本定型|(?:每次|一律|必须|固定|就(?:只)?回|只(?:说|回)|直接(?:说|回)|问一句|我会用|习惯用|爱用|常用|时说).{0,32}[「“"『].{1,40}[」”"』]/.test(
|
|
176
292
|
value,
|
|
177
293
|
);
|
|
178
294
|
}
|
|
295
|
+
|
|
296
|
+
// Stored impressions may contain real preferences and a learned reply script
|
|
297
|
+
// in the same paragraph. Keep the former without replaying the latter as law.
|
|
298
|
+
export function conversationalMemory(content) {
|
|
299
|
+
return String(content || "")
|
|
300
|
+
.split(/(?<=[。!?;;])/)
|
|
301
|
+
.filter((part) => !fixedReplyHabit(part))
|
|
302
|
+
.join("")
|
|
303
|
+
.trim();
|
|
304
|
+
}
|
|
@@ -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,192 @@ 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
|
+
text: m.text,
|
|
46
|
+
status:
|
|
47
|
+
"这是当前说话人对别人的解释;须核对那个人自己的原话,不能视作本人确认",
|
|
48
|
+
})),
|
|
23
49
|
instruction:
|
|
24
|
-
"
|
|
50
|
+
"先读本轮原话、引用和补充,确定谁在对谁说、具体问什么或表达什么。同批有人认真提问或想加入时,别因后面有人刷hh或另一机器人自说自话而漏掉。人和机器人都按实际内容理解,后者的纠正没有裁定别人意思的权力。历史只用于解释这轮,不替换眼前的问题。缩写和省略从同一话题找;多个合理对象就轻问,不把自己的猜测限定成唯二选项。自己的旧话是待核对的记录,不是正确答案或说话模板。",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function dialogueContext(snapshot) {
|
|
55
|
+
const { sourceRows, batch, persona, ...context } = snapshot;
|
|
56
|
+
const inner = { ...context.inner };
|
|
57
|
+
const self = { ...context.self };
|
|
58
|
+
const fresh = new Set(snapshot.batchIds || []);
|
|
59
|
+
const cues = (snapshot.messages || [])
|
|
60
|
+
.filter((m) => fresh.has(m.id) && m.role === "user")
|
|
61
|
+
.map((m) => interestTerms([m.text]));
|
|
62
|
+
const currentWords = (snapshot.messages || [])
|
|
63
|
+
.filter((m) => fresh.has(m.id) && m.role === "user")
|
|
64
|
+
.map((m) => m.text || "")
|
|
65
|
+
.join(" ");
|
|
66
|
+
const recallAsked =
|
|
67
|
+
inner.continuity?.requested ||
|
|
68
|
+
/刚才|之前|上次|记得|说过|答应|约定|那件|那本|那篇|进度|玩到|读到|写到|看过|读过|写过|在推|明明.{0,5}(?:玩|写|读)|(?:为什么|怎么).{0,12}(?:说|叫|喊)/.test(
|
|
69
|
+
currentWords,
|
|
70
|
+
);
|
|
71
|
+
if (inner.continuity && !recallAsked) {
|
|
72
|
+
inner.continuity = {
|
|
73
|
+
...inner.continuity,
|
|
74
|
+
people: (inner.continuity.people || []).map((person) => {
|
|
75
|
+
const selected = { ...person };
|
|
76
|
+
for (const key of [
|
|
77
|
+
"recentShared",
|
|
78
|
+
"myPrivateIntentions",
|
|
79
|
+
"myElsewhereWords",
|
|
80
|
+
"sharedMoments",
|
|
81
|
+
"memories",
|
|
82
|
+
]) {
|
|
83
|
+
if (!Array.isArray(person[key])) continue;
|
|
84
|
+
selected[key] = relevantContext(
|
|
85
|
+
person[key].map((row) => ({
|
|
86
|
+
row,
|
|
87
|
+
content:
|
|
88
|
+
typeof row === "string"
|
|
89
|
+
? row
|
|
90
|
+
: row.text || row.content || JSON.stringify(row),
|
|
91
|
+
})),
|
|
92
|
+
{
|
|
93
|
+
cues,
|
|
94
|
+
limit: 3,
|
|
95
|
+
requireOverlap: true,
|
|
96
|
+
pinned: (item) =>
|
|
97
|
+
/想你|喜欢你|爱你/.test(currentWords) &&
|
|
98
|
+
/想你|喜欢你|爱你/.test(item.content) &&
|
|
99
|
+
!/推游戏|写完|读完|进度|汇报|交稿|发稿|答应.{0,20}(?:玩|读|写)|承诺.{0,20}(?:玩|读|写)/.test(
|
|
100
|
+
item.content,
|
|
101
|
+
),
|
|
102
|
+
},
|
|
103
|
+
).map((item) => item.row);
|
|
104
|
+
}
|
|
105
|
+
return selected;
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const messages = snapshot.messages || [];
|
|
110
|
+
const mustKeep = new Set(
|
|
111
|
+
[
|
|
112
|
+
...messages.slice(-40),
|
|
113
|
+
...currentExchange(snapshot).quoted,
|
|
114
|
+
...messages.filter((m) => fresh.has(m.id)),
|
|
115
|
+
].map((m) => m.id),
|
|
116
|
+
);
|
|
117
|
+
const older = relevantContext(
|
|
118
|
+
messages
|
|
119
|
+
.filter((m) => !mustKeep.has(m.id))
|
|
120
|
+
.map((m) => ({ ...m, content: m.text })),
|
|
121
|
+
{ cues, limit: 10, requireOverlap: true },
|
|
122
|
+
);
|
|
123
|
+
for (const row of older) mustKeep.add(row.id);
|
|
124
|
+
context.messages = messages.filter((m) => mustKeep.has(m.id));
|
|
125
|
+
// Historical compression is still accessible, selected by the current
|
|
126
|
+
// topic. Unrelated weeks of old appraisals must not dominate an ordinary turn.
|
|
127
|
+
for (const key of ["summaries", "stages"]) {
|
|
128
|
+
if (Array.isArray(context[key]))
|
|
129
|
+
context[key] = relevantContext(
|
|
130
|
+
context[key].map((row) => ({ ...row, content: row.summary })),
|
|
131
|
+
{
|
|
132
|
+
cues,
|
|
133
|
+
limit: 2,
|
|
134
|
+
requireOverlap: true,
|
|
135
|
+
pinned: (row) => row.summary === context[key].at(-1)?.summary,
|
|
136
|
+
},
|
|
137
|
+
).map(({ content, ...row }) => row);
|
|
138
|
+
}
|
|
139
|
+
if (socialOnlyBatch(snapshot)) {
|
|
140
|
+
const currentTimes = messages
|
|
141
|
+
.filter((m) => fresh.has(m.id))
|
|
142
|
+
.map((m) => m.time)
|
|
143
|
+
.filter(Number.isFinite);
|
|
144
|
+
const now = currentTimes.length ? Math.max(...currentTimes) : null;
|
|
145
|
+
const quoted = new Set(currentExchange(snapshot).quoted.map((m) => m.id));
|
|
146
|
+
context.messages = context.messages.filter(
|
|
147
|
+
(m) =>
|
|
148
|
+
fresh.has(m.id) ||
|
|
149
|
+
quoted.has(m.id) ||
|
|
150
|
+
(Number.isFinite(now) &&
|
|
151
|
+
Number.isFinite(m.time) &&
|
|
152
|
+
now - m.time <= 30 * 60000),
|
|
153
|
+
);
|
|
154
|
+
context.summaries = [];
|
|
155
|
+
context.stages = [];
|
|
156
|
+
context.addressConventions = (snapshot.summaries || [])
|
|
157
|
+
.flatMap((row) =>
|
|
158
|
+
String(row.summary || "")
|
|
159
|
+
.split(/[。;\n]/)
|
|
160
|
+
.filter((clause) =>
|
|
161
|
+
/称呼约定|互称|私(?:下|聊).{0,12}(?:称|叫|喊)|(?:称|叫|喊).{0,12}(?:仅限|只限|私下|私聊)|宝宝.{0,8}(?:仅限|只叫|只称|只喊)|(?:希望|要求|不喜欢|别用|不要用|少用).{0,25}(?:称呼|叫|喊|开头|口吻|单字|语气)/.test(
|
|
162
|
+
clause,
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
.map((content) => ({ period: row.period, content: content.trim() })),
|
|
166
|
+
)
|
|
167
|
+
.slice(-4);
|
|
168
|
+
context.memories = [];
|
|
169
|
+
delete context.recalled;
|
|
170
|
+
delete context.topics;
|
|
171
|
+
delete inner.onMind;
|
|
172
|
+
delete inner.relatedSelf;
|
|
173
|
+
delete inner.with;
|
|
174
|
+
delete inner.expecting;
|
|
175
|
+
if (inner.state) inner.state = inner.state.replace(/([^)]*)/g, "");
|
|
176
|
+
if (inner.currentLife)
|
|
177
|
+
inner.currentLife = { current: inner.currentLife.current };
|
|
178
|
+
}
|
|
179
|
+
if (self.here) {
|
|
180
|
+
const recalled = relevantContext(
|
|
181
|
+
[{ content: conversationalMemory(self.here) }],
|
|
182
|
+
{ cues, limit: 1, requireOverlap: true },
|
|
183
|
+
);
|
|
184
|
+
if (recalled.length)
|
|
185
|
+
inner.placeMemory = {
|
|
186
|
+
content: recalled[0].content,
|
|
187
|
+
status: "过去对这个地方的印象,不是人格定义或回复规则,可以有误",
|
|
188
|
+
};
|
|
189
|
+
delete self.here;
|
|
190
|
+
}
|
|
191
|
+
// Planning reasons and prior appraisal reports are useful to the life
|
|
192
|
+
// planner, but repeatedly priming ordinary speech with them creates scripts.
|
|
193
|
+
delete inner.stood;
|
|
194
|
+
delete inner.will;
|
|
195
|
+
if (inner.currentLife) {
|
|
196
|
+
const life = inner.currentLife;
|
|
197
|
+
const compact = (row) => ({
|
|
198
|
+
id: row.id,
|
|
199
|
+
title: row.title,
|
|
200
|
+
state: row.state,
|
|
201
|
+
...(row.wait ? { wait: row.wait } : {}),
|
|
202
|
+
...(row.plannedFor ? { plannedFor: row.plannedFor } : {}),
|
|
203
|
+
});
|
|
204
|
+
inner.currentLife = {
|
|
205
|
+
...life,
|
|
206
|
+
pending: (life.pending || []).map(compact),
|
|
207
|
+
intentions: (life.intentions || []).map(compact),
|
|
208
|
+
worksCoverage:
|
|
209
|
+
"以下是按本轮话题选出的作品片段,不是全部历史;缺少一篇不能证明从未写过或读过。",
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return {
|
|
213
|
+
...context,
|
|
214
|
+
self,
|
|
215
|
+
inner,
|
|
216
|
+
grounding: conversationGrounding(snapshot),
|
|
25
217
|
};
|
|
26
218
|
}
|
|
27
219
|
|