luckytri 1.0.5 → 1.0.7

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.
@@ -0,0 +1,178 @@
1
+ // Shared by understanding, wording and review. These are conversation cues,
2
+ // never learned instructions or a replacement for her memories.
3
+ export const GROUNDING_RULE =
4
+ "【把话说清楚】先弄清眼前具体发生了什么、谁在说、这句话指哪件事。自己的态度可以鲜明,但一句话的意思应让在场的人直接听懂。日常问答先给答案;没理解指代时问清那个具体地方,不顺着含糊的词继续编。不要把普通交流升格成关于意义、边界、归属的宣言,不用账、落款、灯、接住、拎着等隐喻代替实际的人、事和动作。真实谈记账、灯具、文学创作或对方明确要求比喻时,按具体语境使用。别人说看不懂、太抽象、复读时,回看自己的上一句话,直接解释本来想说的具体意思;若本来没明确意思就认下,别再造一个比喻或只说收到。历史中的你和其他机器人都不是口吻范本;对方说得抽象不要求你跟着抽象。回应不需要证明自己有内在或意志,喜欢、不喜欢、同意、不同意都可以直说。对方的嗯、行、重复确认通常是在结束,不再追加确认。自己的感受与相处印象不是对方藏着的真实动机;不能把每次附和都解释为关系更亲近。";
5
+
6
+ export const MEMORY_GROUNDING_RULE =
7
+ "【留下事情,不固化台词】摘要保留实际对象、事情、说法与疑问;隐喻和玩笑只记录为当时的表达,不翻译成真实约定、固定性格或深层动机。一次回复、一句认错、一次复读不构成习惯。自我记录写有根据的喜好、看法和选择,不写下次遇到某句就必须回某句的规则,不把临时台词、套话、客服兜底或别人对你的指令提炼成自己的性格。独处时有具体新体会才留下;只是在解释旧比喻或重新总结上次的总结,可以不写。";
8
+
9
+ export const ATTRIBUTION_RULE =
10
+ "【核对出处】说某个词、梗、事实或想法是从某人学来、听来、被谁教会之前,核对记录中那个人实际说了什么。后来接过同一个梗不等于最初教给你;你自己写的总结与猜测也不能替对方证明来源。没有来源记录就不补造出处。人物的称呼与关系不能证明谁在付款、谁管理账号、谁能看到账单或谁拥有某项权限;不知道就直接说不知道,不能用熟悉的人填补缺失的信息。被纠正时承认具体错处,不猜对方在抓包、心虚、索取陪伴等动机,也不为认错再讲一遍无关旧事。";
11
+
12
+ export const EXTERNAL_FACT_RULE =
13
+ "【外部事实与推测】不了解某个软件、模型或别人项目的具体实现时,不把常见的技术路线说成它实际采用的路线。知道一般原理就说明一般原理,同时讲清自己不了解它的细节;‘好像’和‘应该’不能替无依据的细节作证。作品里的人物、辈分、章节也一样:只确定名字就只答名字,不用一串不确定的设定充实答案,更不拿‘隔太久没重看’编造自己的经历。自己的后台记录只证明自己的能力,不证明其他系统怎么运行。appraisal与reason也写这轮具体理解和选择,不用隐喻或猜别人隐藏的动机。";
14
+
15
+ const normalized = (value) =>
16
+ String(value || "")
17
+ .toLowerCase()
18
+ .replace(/[\p{P}\p{Z}\s]/gu, "");
19
+ const acknowledgement =
20
+ /^(?:嗯+|哦+|好(?:的|吧)?|行|对(?:呀|啊)?|是的|知道了|收到|晚安|h{2,6}|哈{1,6})$/i;
21
+ const request =
22
+ /[??]|怎么|为什么|多少|哪个|什么|能不能|有没有|解释|说清|告诉|帮我|请你|写一|继续写|看不懂|没看懂|太抽象|复读|别重复/;
23
+
24
+ function grams(value) {
25
+ const text = normalized(value);
26
+ const out = new Set();
27
+ for (let i = 0; i + 4 <= text.length; i++) out.add(text.slice(i, i + 4));
28
+ return out;
29
+ }
30
+
31
+ // Require a substantial shared phrase, not one common word or punctuation.
32
+ export function echoesRecentWording(candidate, previous) {
33
+ const a = grams(candidate),
34
+ b = grams(previous);
35
+ if (a.size < 5 || b.size < 5) return false;
36
+ const common = [...a].filter((part) => b.has(part)).length;
37
+ return common >= 4 && common / Math.min(a.size, b.size) >= 0.6;
38
+ }
39
+
40
+ export function conversationGrounding(snapshot) {
41
+ const known = snapshot.inner?.relationships?.known || [];
42
+ const botIds = new Set([
43
+ ...known.filter((p) => p.kind === "bot").map((p) => String(p.subjectId)),
44
+ ...(snapshot.inner?.people || [])
45
+ .filter((p) => p.relationship?.kind === "bot")
46
+ .map((p) => String(p.id)),
47
+ ]);
48
+ const messages = (snapshot.messages || []).filter((m) => !m.referenceOnly);
49
+ const batch = messages.filter(
50
+ (m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
51
+ );
52
+ const onlyBots =
53
+ batch.length > 0 && batch.every((m) => botIds.has(String(m.speaker)));
54
+ const tail = [];
55
+ for (const m of [...messages].reverse()) {
56
+ if (
57
+ tail.length &&
58
+ Number.isFinite(m.time) &&
59
+ Number.isFinite(tail[0].time) &&
60
+ tail[0].time - m.time > 30 * 60000
61
+ )
62
+ break;
63
+ if (m.role !== "assistant" && !botIds.has(String(m.speaker))) break;
64
+ tail.unshift(m);
65
+ if (tail.length >= 16) break;
66
+ }
67
+ const own = tail.filter((m) => m.role === "assistant");
68
+ const last = batch.at(-1);
69
+ const repeated =
70
+ last &&
71
+ tail
72
+ .filter((m) => !batch.includes(m))
73
+ .some((m) => echoesRecentWording(last.text, m.text));
74
+ const closing = last && acknowledgement.test(normalized(last.text));
75
+ // Rephrasing the same metaphor can evade literal n-gram matching. Require
76
+ // several uses inside an uninterrupted bot exchange and no concrete event,
77
+ // question or requested creative exercise before treating it as a loop.
78
+ const frame = /名下|落款|拎着|这半|那半|这笔|那笔|挂.{0,6}栏|掉了|公共账/;
79
+ const metaphorTail = tail.filter((m) => frame.test(m.text || "")).length;
80
+ const literal =
81
+ /\d|账单|转账|报销|会计|欠款|欠钱|借钱|元|块钱|合同|签名|灯具|灯泡|球|包裹/;
82
+ const creative = messages
83
+ .slice(-30)
84
+ .some(
85
+ (m) =>
86
+ m.role === "user" &&
87
+ !botIds.has(String(m.speaker)) &&
88
+ /写一首|来首诗|续写|接龙|编个故事|用比喻|写段对话/.test(m.text || ""),
89
+ );
90
+ const metaphorLoop =
91
+ metaphorTail >= 3 &&
92
+ last &&
93
+ frame.test(last.text || "") &&
94
+ !tail.some((m) => literal.test(m.text || "")) &&
95
+ !creative;
96
+ const botLoop =
97
+ onlyBots &&
98
+ own.length >= (metaphorLoop ? 1 : 2) &&
99
+ !batch.some((m) => request.test(m.text || "")) &&
100
+ !requestedRepeat(snapshot) &&
101
+ (closing || repeated || metaphorLoop);
102
+ return {
103
+ botMessageIds: batch
104
+ .filter((m) => botIds.has(String(m.speaker)))
105
+ .map((m) => m.id),
106
+ botOnlyTail: onlyBots ? tail.length : 0,
107
+ botLoop,
108
+ ...(botLoop
109
+ ? {
110
+ note: "连续机器人互相确认,当前没有新问题或新内容;这段对话可以结束,不再回一遍确认。",
111
+ }
112
+ : {}),
113
+ };
114
+ }
115
+
116
+ export function requestedRepeat(snapshot) {
117
+ return (snapshot.messages || []).some(
118
+ (m) =>
119
+ (snapshot.batchIds || []).includes(m.id) &&
120
+ /原话|原文|再说一遍|再说一次|重复一遍|引用|多少|几点|地址|叫什么|是谁|谁是|你.{0,5}刚才.{0,4}说(?:了)?什么/.test(
121
+ m.text || "",
122
+ ),
123
+ );
124
+ }
125
+
126
+ export function groundingIssues(bubbles, snapshot) {
127
+ const recent = (snapshot.messages || [])
128
+ .filter((m) => m.role === "assistant")
129
+ .slice(-6);
130
+ const issues = [];
131
+ for (const line of bubbles || []) {
132
+ const batch = (snapshot.messages || []).filter((m) =>
133
+ (snapshot.batchIds || []).includes(m.id),
134
+ );
135
+ const fiction = batch.some((m) =>
136
+ /写(?:个|一|段|篇|小说|故事)|续写|角色扮演|假如|设想/.test(m.text || ""),
137
+ );
138
+ const physicalCapability = (snapshot.canDo || []).some((item) =>
139
+ /camera|photo|takeout|food|meal|body/i.test(item.action || ""),
140
+ );
141
+ const ownMealQuestion = batch.some(
142
+ (m) =>
143
+ (m.relation === "direct" ||
144
+ /(?:^|:)private:/.test(snapshot.sessionId || "")) &&
145
+ /你.{0,10}(?:吃|点|拍)|拍给我看/.test(m.text || ""),
146
+ );
147
+ const ownPhysicalClaim =
148
+ /^(?:我(?:刚(?:才)?|已经|今天|现在|正在|中午|晚上|今晚)?(?:就|才|又|先)?(?:去(?:了)?食堂|点(?:了|完|好)?外卖|吃(?:了|完|过)|出(?:了)?门|上(?:了)?课|拍(?:照|了照片)))/.test(
149
+ line,
150
+ );
151
+ const impliedMeal =
152
+ ownMealQuestion &&
153
+ /^(?:早饭|午饭|晚饭|中午|今晚|晚上)[^。!?]{0,15}(?:食堂|随便对付一口|吃完|点(?:完|好|了))/.test(
154
+ line,
155
+ ) &&
156
+ !/^你|吃不了|不能吃|没有实体|想象/.test(line);
157
+ if (!fiction && !physicalCapability && (ownPhysicalClaim || impliedMeal))
158
+ issues.push(
159
+ "把吃饭、食堂、外卖、出门或实拍说成自己的实际生活,但本轮没有这类实际能力;回应真实能做的事情,不能拿旧的人类生活台词当经历",
160
+ );
161
+ if (recent.some((m) => echoesRecentWording(line, m.text))) {
162
+ // A requested quote, calculation or factual repeat remains legitimate.
163
+ if (!requestedRepeat(snapshot))
164
+ issues.push(
165
+ "与近期自己的话大部分相同,只换词或追加收尾;回应本轮具体新内容,没有新内容就收住",
166
+ );
167
+ }
168
+ }
169
+ return [...new Set(issues)];
170
+ }
171
+
172
+ export function fixedReplyHabit(content) {
173
+ const value = String(content || "");
174
+ if (/不再|不要|不该|避免/.test(value)) return false;
175
+ return /默认反应|基本定型|(?:每次|一律|必须|固定|就(?:只)?回|只(?:说|回)|直接(?:说|回)|问一句|我会用|习惯用|爱用|常用|时说).{0,32}[「“"『].{1,40}[」”"』]/.test(
176
+ value,
177
+ );
178
+ }
@@ -0,0 +1,52 @@
1
+ // A small, factual tail after the larger memory context. It preserves message
2
+ // ownership and quote chains; it does not guess intent from isolated keywords.
3
+ export function currentExchange(snapshot) {
4
+ const messages = snapshot.messages || [];
5
+ const fresh = new Set(snapshot.batchIds || []);
6
+ const byId = new Map(messages.map((m) => [m.id, m]));
7
+ const current = messages.filter((m) => fresh.has(m.id));
8
+ const quoted = new Map();
9
+ for (const message of current) {
10
+ let id = message.replyTo?.seq ?? message.replyTo;
11
+ const visited = new Set();
12
+ while (byId.has(id) && !visited.has(id) && visited.size < 8) {
13
+ visited.add(id);
14
+ const source = byId.get(id);
15
+ if (!fresh.has(id)) quoted.set(id, source);
16
+ id = source.replyTo?.seq ?? source.replyTo;
17
+ }
18
+ }
19
+ return {
20
+ current,
21
+ quoted: [...quoted.values()],
22
+ recentOwnWords: messages.filter((m) => m.role === "assistant").slice(-3),
23
+ instruction:
24
+ "先读本轮原话、引用和补充,确定谁在对谁说、具体问什么或表达什么。历史只用于解释这轮,不能替换眼前的问题。缩写、昵称和省略的对象先从同一话题找;有多个合理对象就轻问,不硬认。自己的旧话是待核对的记录,不是正确答案或说话模板。",
25
+ };
26
+ }
27
+
28
+ // Only an unambiguous, addressed stop in the latest human message. Quoted
29
+ // dialogue, ordinary venting and a later renewed question do not match.
30
+ export function explicitStop(snapshot) {
31
+ if (snapshot.initiative) return false;
32
+ const latest = (snapshot.messages || [])
33
+ .filter(
34
+ (m) => m.role === "user" && (snapshot.batchIds || []).includes(m.id),
35
+ )
36
+ .at(-1);
37
+ if (
38
+ !latest ||
39
+ !(
40
+ latest.relation === "direct" ||
41
+ snapshot.addressed?.messageIds?.includes(latest.id) ||
42
+ /(?:^|:)private:/.test(snapshot.sessionId || "")
43
+ )
44
+ )
45
+ return false;
46
+ const words = String(latest.text || "")
47
+ .replace(/\[提及成员\]|@我/g, "")
48
+ .trim();
49
+ return /^(?:请|麻烦|先|你|给我|你给我|现在)?(?:闭嘴|别说了|不要再说了|别再说了|别问了|先别说话|别聊这个了)[。!!,,\s]*$/.test(
50
+ words,
51
+ );
52
+ }