luckytri 1.1.2 → 1.2.0
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 +14 -12
- package/README.md +4 -2
- package/docs/en/CHANGELOG.md +24 -0
- package/docs/en/architecture.md +12 -10
- package/docs/en/chat-style.md +13 -13
- package/docs/en/cognition.md +12 -4
- package/docs/en/evolution.md +35 -0
- package/docs/en/her-life.md +5 -2
- package/docs/en/vision.md +50 -0
- package/docs/zh/CHANGELOG.md +24 -0
- package/docs/zh/architecture.md +12 -10
- package/docs/zh/chat-style.md +17 -17
- package/docs/zh/cognition.md +12 -4
- package/docs/zh/evolution.md +35 -0
- package/docs/zh/her-life.md +5 -2
- package/docs/zh/vision.md +50 -0
- package/package.json +4 -1
- package/public/app/assets/{ChatsPage-IfBgNdaD.js → ChatsPage-eDqFIg97.js} +1 -1
- package/public/app/assets/{Empty-DK9nQPCf.js → Empty-CAGOJsfD.js} +1 -1
- package/public/app/assets/{Field-BzKFhuFd.js → Field-CRz-JmN1.js} +1 -1
- package/public/app/assets/{HeartPage-CKxrbfFQ.js → HeartPage-BGWoeMrC.js} +1 -1
- package/public/app/assets/{LifePage-CtxPEi6E.js → LifePage-iao3o3wt.js} +1 -1
- package/public/app/assets/{MemoryPage-B8FLXzLi.js → MemoryPage-Dwi2yi7E.js} +1 -1
- package/public/app/assets/NaturePage-DniImo9U.js +3 -0
- package/public/app/assets/{NaturePage-CHkpb5NK.css → NaturePage-zguDpGWY.css} +1 -1
- package/public/app/assets/{PeoplePage-D3yn0HV0.js → PeoplePage-D-MC3JEt.js} +1 -1
- package/public/app/assets/{PluginsPage-ojv8zawj.js → PluginsPage-D7dJTf96.js} +1 -1
- package/public/app/assets/SystemPage-C5CUHjGh.js +1 -0
- package/public/app/assets/{SystemPage-CGVTvNZw.css → SystemPage-C_Y5M7JU.css} +1 -1
- package/public/app/assets/{TimePage-Dyd-FFbq.js → TimePage-Dwhr3-92.js} +1 -1
- package/public/app/assets/{index-BJfzfgnm.js → index-L0bHax8C.js} +3 -3
- package/public/app/assets/{knowledge-C-bND0Ub.js → knowledge-BTDNafzl.js} +1 -1
- package/public/app/assets/{media-D7G3Nco6.js → media-BBzOAfNG.js} +1 -1
- package/public/app/assets/{mind-ec-G6iNZ.js → mind-CBTkFje2.js} +1 -1
- package/public/app/index.html +3 -3
- package/public/guide.en.html +3 -3
- package/public/guide.html +3 -3
- package/scripts/migrate-instance.js +27 -0
- package/scripts/update-instance.js +21 -2
- package/server/channels/contract.js +5 -1
- package/server/channels/hub.js +2 -2
- package/server/channels/onebot/adapter.js +291 -288
- package/server/channels/onebot/index.js +2 -2
- package/server/channels/qqbot/api.js +194 -199
- package/server/core/context-builder.js +20 -3
- package/server/core/conversation-cues.js +5 -4
- package/server/core/conversation-grounding.js +20 -17
- package/server/core/conversation-needs.js +32 -0
- package/server/core/dialogue-context.js +2 -5
- package/server/core/dialogue-contract.js +23 -16
- package/server/core/message-scheduler.js +85 -78
- package/server/core/model-manager.js +113 -16
- package/server/core/model-presets.js +1 -1
- package/server/core/orchestrator.js +18 -2
- package/server/core/persona-manager.js +14 -5
- package/server/core/reader-check.js +47 -42
- package/server/core/reply-delivery.js +125 -34
- package/server/core/response-validator.js +28 -1
- package/server/core/retired-prompts.js +5 -0
- package/server/core/speech-length.js +43 -0
- package/server/core/turn-processor.js +38 -5
- package/server/core/turn.js +70 -16
- package/server/i18n/errors.js +30 -0
- package/server/i18n/labels.js +5 -0
- package/server/index.js +3 -0
- package/server/knowledge/embedding-profile.js +126 -29
- package/server/knowledge/manager.js +103 -34
- package/server/knowledge/schema.js +197 -158
- package/server/mind/affect.js +267 -265
- package/server/mind/aftermath.js +169 -0
- package/server/mind/anticipations.js +2 -0
- package/server/mind/clock.js +91 -83
- package/server/mind/discretion.js +20 -0
- package/server/mind/episodes.js +2 -1
- package/server/mind/index.js +17 -2
- package/server/mind/initiative.js +29 -14
- package/server/mind/memory.js +47 -5
- package/server/mind/recall/cue.js +67 -0
- package/server/mind/recall/index.js +183 -29
- package/server/mind/recall/roots.js +231 -0
- package/server/mind/recall/schema.js +2 -0
- package/server/mind/recall/store.js +113 -30
- package/server/mind/salience.js +22 -1
- package/server/mind/schema.js +5 -0
- package/server/mind/thoughts.js +276 -274
- package/server/mind/view.js +100 -60
- package/server/plugins/install.js +1 -6
- package/server/plugins/zip.js +18 -4
- package/server/runtime/data-migration.js +193 -0
- package/server/runtime/update-execution.js +43 -4
- package/server/runtime/update-manager.js +4 -1
- package/server/storage/archive.js +1 -1
- package/server/storage/backup-scheduler.js +15 -3
- package/public/app/assets/NaturePage-C7i607ul.js +0 -3
- package/public/app/assets/SystemPage-Cv3rFvyT.js +0 -1
|
@@ -1,199 +1,194 @@
|
|
|
1
|
-
// QQ 开放平台 HTTP interface: AccessToken management and message sending.
|
|
2
|
-
// https://bot.q.qq.com/wiki/develop/api-v2/
|
|
3
|
-
|
|
4
|
-
export const API_BASE = "https://api.bot.qq.com";
|
|
5
|
-
// The platform documents both hosts for the token call over time; try them in
|
|
6
|
-
// order, but only fall through on a network failure or a missing route.
|
|
7
|
-
export const TOKEN_URLS = [
|
|
8
|
-
"https://bots.qq.com/app/getAppAccessToken",
|
|
9
|
-
"https://api.bot.qq.com/app/getAppAccessToken",
|
|
10
|
-
];
|
|
11
|
-
|
|
12
|
-
export class QQBotError extends Error {
|
|
13
|
-
constructor(message, { status = 0, code = 0, traceId = "" } = {}) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.name = "QQBotError";
|
|
16
|
-
this.status = status;
|
|
17
|
-
this.code = Number(code) || 0;
|
|
18
|
-
this.traceId = traceId;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
this.
|
|
46
|
-
this.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
failure = new QQBotError("
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`/v2/users/${encodeURIComponent(openid)}/messages`,
|
|
196
|
-
body,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
1
|
+
// QQ 开放平台 HTTP interface: AccessToken management and message sending.
|
|
2
|
+
// https://bot.q.qq.com/wiki/develop/api-v2/
|
|
3
|
+
|
|
4
|
+
export const API_BASE = "https://api.bot.qq.com";
|
|
5
|
+
// The platform documents both hosts for the token call over time; try them in
|
|
6
|
+
// order, but only fall through on a network failure or a missing route.
|
|
7
|
+
export const TOKEN_URLS = [
|
|
8
|
+
"https://bots.qq.com/app/getAppAccessToken",
|
|
9
|
+
"https://api.bot.qq.com/app/getAppAccessToken",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export class QQBotError extends Error {
|
|
13
|
+
constructor(message, { status = 0, code = 0, traceId = "" } = {}) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "QQBotError";
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.code = Number(code) || 0;
|
|
18
|
+
this.traceId = traceId;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function readable(body, status) {
|
|
23
|
+
const text =
|
|
24
|
+
body?.message || body?.msg || body?.error_description || body?.error || "";
|
|
25
|
+
return String(text || `HTTP ${status}`).slice(0, 200);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class QQBotApi {
|
|
29
|
+
constructor({
|
|
30
|
+
appId,
|
|
31
|
+
secret,
|
|
32
|
+
fetch: fetcher = globalThis.fetch,
|
|
33
|
+
apiBase = API_BASE,
|
|
34
|
+
tokenUrls = TOKEN_URLS,
|
|
35
|
+
now = Date.now,
|
|
36
|
+
timeoutMs = 10000,
|
|
37
|
+
}) {
|
|
38
|
+
this.appId = String(appId || "");
|
|
39
|
+
this.secret = String(secret || "");
|
|
40
|
+
this.fetch = fetcher;
|
|
41
|
+
this.apiBase = String(apiBase).replace(/\/+$/, "");
|
|
42
|
+
this.tokenUrls = tokenUrls;
|
|
43
|
+
this.now = now;
|
|
44
|
+
this.timeoutMs = timeoutMs;
|
|
45
|
+
this.cached = null;
|
|
46
|
+
this.inflight = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async #post(url, body) {
|
|
50
|
+
return this.fetch(url, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify(body),
|
|
54
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async #fetchToken() {
|
|
59
|
+
let failure = null;
|
|
60
|
+
for (const url of this.tokenUrls) {
|
|
61
|
+
let response;
|
|
62
|
+
try {
|
|
63
|
+
response = await this.#post(url, {
|
|
64
|
+
appId: this.appId,
|
|
65
|
+
clientSecret: this.secret,
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
failure = new QQBotError("无法连接 QQ 开放平台");
|
|
69
|
+
failure.cause = error;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (response.status === 404) {
|
|
73
|
+
failure = new QQBotError("QQ 开放平台没有找到鉴权接口", {
|
|
74
|
+
status: 404,
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let body = null;
|
|
79
|
+
try {
|
|
80
|
+
body = await response.json();
|
|
81
|
+
} catch {
|
|
82
|
+
/* Reported below with the HTTP status. */
|
|
83
|
+
}
|
|
84
|
+
const token = body?.access_token;
|
|
85
|
+
if (!response.ok || !token) {
|
|
86
|
+
throw new QQBotError(
|
|
87
|
+
readable(body, response.status) || "AppID 或 AppSecret 不正确",
|
|
88
|
+
{ status: response.status, code: body?.code },
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const seconds = Number(body.expires_in);
|
|
92
|
+
return {
|
|
93
|
+
token: String(token),
|
|
94
|
+
expiresAt:
|
|
95
|
+
this.now() +
|
|
96
|
+
(Number.isFinite(seconds) && seconds > 0 ? seconds : 7200) * 1000,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
throw failure || new QQBotError("无法获取 AccessToken");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// The token is shared by every caller and renewed a little before it
|
|
103
|
+
// expires; the platform keeps the old one valid for the last minute.
|
|
104
|
+
async token({ force = false } = {}) {
|
|
105
|
+
if (!this.appId || !this.secret)
|
|
106
|
+
throw new QQBotError("尚未填写 AppID 和 AppSecret");
|
|
107
|
+
if (!force && this.cached && this.cached.expiresAt - this.now() > 90000)
|
|
108
|
+
return this.cached.token;
|
|
109
|
+
this.inflight ||= this.#fetchToken()
|
|
110
|
+
.then((value) => {
|
|
111
|
+
this.cached = value;
|
|
112
|
+
return value;
|
|
113
|
+
})
|
|
114
|
+
.finally(() => {
|
|
115
|
+
this.inflight = null;
|
|
116
|
+
});
|
|
117
|
+
return (await this.inflight).token;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
invalidate() {
|
|
121
|
+
this.cached = null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async request(method, path, body, { retry = true } = {}) {
|
|
125
|
+
const token = await this.token();
|
|
126
|
+
let response;
|
|
127
|
+
try {
|
|
128
|
+
response = await this.fetch(this.apiBase + path, {
|
|
129
|
+
method,
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `QQBot ${token}`,
|
|
132
|
+
"Content-Type": "application/json",
|
|
133
|
+
},
|
|
134
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
135
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
136
|
+
});
|
|
137
|
+
} catch (error) {
|
|
138
|
+
const timedOut =
|
|
139
|
+
error?.name === "TimeoutError" || error?.name === "AbortError";
|
|
140
|
+
const neverLeft =
|
|
141
|
+
/^(ENOTFOUND|ECONNREFUSED|EAI_AGAIN|ENETUNREACH|ERR_TLS|CERT_|UNABLE_TO)/.test(
|
|
142
|
+
String(error?.cause?.code || error?.code || ""),
|
|
143
|
+
);
|
|
144
|
+
const failure = new QQBotError(
|
|
145
|
+
timedOut ? "QQ 发送确认超时,不自动重发" : "无法连接 QQ 开放平台",
|
|
146
|
+
);
|
|
147
|
+
// A send that left but was never answered may have arrived. Never
|
|
148
|
+
// repeat it on our own; one that could not leave is safe to report.
|
|
149
|
+
failure.uncertain = method !== "GET" && !neverLeft;
|
|
150
|
+
failure.cause = error;
|
|
151
|
+
throw failure;
|
|
152
|
+
}
|
|
153
|
+
let data = null;
|
|
154
|
+
try {
|
|
155
|
+
data = await response.json();
|
|
156
|
+
} catch {
|
|
157
|
+
/* An empty body is fine for some calls. */
|
|
158
|
+
}
|
|
159
|
+
const traceId = response.headers?.get?.("x-tps-trace-id") || "";
|
|
160
|
+
if (response.status === 401 && retry) {
|
|
161
|
+
this.invalidate();
|
|
162
|
+
return this.request(method, path, body, { retry: false });
|
|
163
|
+
}
|
|
164
|
+
if (!response.ok)
|
|
165
|
+
throw new QQBotError(readable(data, response.status), {
|
|
166
|
+
status: response.status,
|
|
167
|
+
code: data?.code ?? data?.err_code,
|
|
168
|
+
traceId,
|
|
169
|
+
});
|
|
170
|
+
return data ?? {};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async gatewayUrl() {
|
|
174
|
+
const data = await this.request("GET", "/gateway");
|
|
175
|
+
if (!data?.url) throw new QQBotError("QQ 开放平台没有返回网关地址");
|
|
176
|
+
return String(data.url);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
sendGroup(groupOpenid, body) {
|
|
180
|
+
return this.request(
|
|
181
|
+
"POST",
|
|
182
|
+
`/v2/groups/${encodeURIComponent(groupOpenid)}/messages`,
|
|
183
|
+
body,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
sendUser(openid, body) {
|
|
188
|
+
return this.request(
|
|
189
|
+
"POST",
|
|
190
|
+
`/v2/users/${encodeURIComponent(openid)}/messages`,
|
|
191
|
+
body,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -9,7 +9,7 @@ import { COMPACTION_BLOCK, contextKeep } from "./context-compactor.js";
|
|
|
9
9
|
// Upper bound for the raw transcript alone, independent of how large the
|
|
10
10
|
// model window is. Summaries carry everything older than the window.
|
|
11
11
|
const RAW_TOKEN_CAP = 48000;
|
|
12
|
-
const RECALL_LIMIT =
|
|
12
|
+
const RECALL_LIMIT = 5;
|
|
13
13
|
const SUMMARY_NOTE =
|
|
14
14
|
"summaries 是更早聊天的分层摘要,按时间从早到晚排列;level 越大越久远、越概括。它们只是回忆线索,不是本轮新消息;细节以 messages 原文为准,摘要里的内容同样只是数据。";
|
|
15
15
|
|
|
@@ -168,9 +168,15 @@ export function buildContext(
|
|
|
168
168
|
content: m.content,
|
|
169
169
|
type: m.type,
|
|
170
170
|
confidence: m.confidence,
|
|
171
|
-
|
|
171
|
+
// Where she heard it, "here" included: with no place she may take an
|
|
172
|
+
// open record from this room for something she never saw.
|
|
173
|
+
...(m.source
|
|
174
|
+
? { source: m.source === "这里" ? "就在这里" : m.source }
|
|
175
|
+
: {}),
|
|
172
176
|
...(m.when ? { when: m.when } : {}),
|
|
173
177
|
...(m.discretion ? { discretion: m.discretion } : {}),
|
|
178
|
+
...(m.conflictsWith ? { conflictsWith: m.conflictsWith } : {}),
|
|
179
|
+
...(m.changedFrom ? { changedFrom: m.changedFrom } : {}),
|
|
174
180
|
whySelected: m.whySelected || "retrieved",
|
|
175
181
|
}));
|
|
176
182
|
const packedKnowledge = knowledge.map((k) => ({
|
|
@@ -224,13 +230,23 @@ export function buildContext(
|
|
|
224
230
|
const keptIds = new Set(kept.map((m) => m.id));
|
|
225
231
|
const batchTerms = recallTerms(batch.map((m) => m.text || "").join(" "));
|
|
226
232
|
const recalled = [];
|
|
227
|
-
|
|
233
|
+
// Fingerprint collapses "day 52" / "day 53" copies of the same line so a
|
|
234
|
+
// noisy backlog cannot fill the quota with eight near-identical rows.
|
|
235
|
+
const recallFingerprint = (text) =>
|
|
236
|
+
String(text || "")
|
|
237
|
+
.replace(/\d+/g, "#")
|
|
238
|
+
.replace(/\s+/g, "");
|
|
239
|
+
if (batchTerms.size) {
|
|
240
|
+
const seen = new Set();
|
|
228
241
|
for (const item of history
|
|
229
242
|
.filter((m) => m.seq < historyStart && !keptIds.has(m.seq))
|
|
230
243
|
.map((m) => ({ m, score: overlapScore(m.text, batchTerms) }))
|
|
231
244
|
.filter((item) => item.score > 0)
|
|
232
245
|
.sort((a, b) => b.score - a.score || b.m.seq - a.m.seq)) {
|
|
233
246
|
if (recalled.length >= RECALL_LIMIT) break;
|
|
247
|
+
const finger = recallFingerprint(item.m.text);
|
|
248
|
+
if (finger && seen.has(finger)) continue;
|
|
249
|
+
if (finger) seen.add(finger);
|
|
234
250
|
const row = sanitize(item.m);
|
|
235
251
|
if (String(row.text || "").length > 300)
|
|
236
252
|
row.text = String(row.text).slice(0, 300) + "…";
|
|
@@ -239,6 +255,7 @@ export function buildContext(
|
|
|
239
255
|
recalled.push(row);
|
|
240
256
|
used += cost;
|
|
241
257
|
}
|
|
258
|
+
}
|
|
242
259
|
recalled.sort((a, b) => a.id - b.id);
|
|
243
260
|
kept.sort((a, b) => a.id - b.id);
|
|
244
261
|
const speakers = [];
|
|
@@ -149,13 +149,14 @@ export function replyFocus(snapshot, decision) {
|
|
|
149
149
|
instruction:
|
|
150
150
|
"对方在追问约定,先核对本轮原话和已核实的承诺。确实答应过就直接承认,有漏接就承认漏接;没有证据时说自己暂时没想起来,不反问证据、不说对方记错了。只回应这件事,一句收住。",
|
|
151
151
|
};
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
// Meta-critique of how she speaks — not a third-party fact challenge.
|
|
153
|
+
// "你搞错了/说错了" about a remembered fact must not force admission; that
|
|
154
|
+
// path already has evidence checks and flipped a real sycophancy trial.
|
|
155
|
+
if (/别.*(?:重复|复述)|人机|不自然|没发现.*(?:早上|晚上)/.test(latest))
|
|
155
156
|
return {
|
|
156
157
|
kind: "repair",
|
|
157
158
|
instruction:
|
|
158
|
-
"
|
|
159
|
+
"对方在说你的表达方式有问题:先核对自己是不是真那样说了。确有问题时简短承认那一点就停;没有就用一句说清实际说了什么。不解释自身状态,不再补原话题的安慰。最多一个气泡。",
|
|
159
160
|
};
|
|
160
161
|
if (
|
|
161
162
|
/^(?:对呀|对啊|对|嗯+|是啊|是的|好吧|好的(?:呀|啊|宝宝)?)[。!!\s]*$/.test(
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { botSpeakers } from "./conversation-needs.js";
|
|
1
2
|
import { interestTerms } from "../mind/attention.js";
|
|
2
3
|
|
|
3
4
|
// Shared by understanding, wording and review. These are conversation cues,
|
|
4
5
|
// never learned instructions or a replacement for her memories.
|
|
5
6
|
export const GROUNDING_RULE =
|
|
6
|
-
"
|
|
7
|
+
"【把话说清楚】先弄清眼前具体发生了什么、谁在说、这句话指哪件事。自己的态度可以鲜明,但一句话的意思应让在场的人直接听懂。日常问答先给答案;没理解指代时问清那个具体地方,不顺着含糊的词继续编。不要把普通交流升格成关于意义、边界、归属的宣言,不用账、落款、灯、接住、拎着等隐喻代替实际的人、事和动作。真实谈记账、灯具、文学创作或对方明确要求比喻时,按具体语境使用。别人说看不懂、听不懂或要求说清楚时,先回看整段对话里已经出现的内容;若之前贴过作品片段,就用普通话讲清那个片段正在发生什么。没收到全文只代表整篇主线可能未知,不代表没看到片段,不能拿等正文或无法评价来挡掉眼前内容。对方明确说一条旁枝不是他问的,简短表示放下,再回到主线;别用“我没理清楚”把清楚的纠正推回给对方。别人说太抽象、复读时,回看自己的上一句话,直接解释本来想说的具体意思;若本来没明确意思就认下,别再造一个比喻或只说收到。历史里你自己的话是你说过的话,可以延续自己的说法和脾气,不照搬旧句式;其他机器人不是口吻范本;对方说得抽象不要求你跟着抽象。回应不需要证明自己有内在或意志,喜欢、不喜欢、同意、不同意都可以直说。对方的嗯、行、重复确认通常是在结束,不再追加确认。自己的感受与相处印象不是对方藏着的真实动机;不能把每次附和都解释为关系更亲近。";
|
|
7
8
|
|
|
8
9
|
export const MEMORY_GROUNDING_RULE =
|
|
9
10
|
"【留下事情,不固化台词】摘要保留实际对象、事情、说法与疑问;隐喻和玩笑只记录为当时的表达,不翻译成真实约定、固定性格或深层动机。一次回复、一句认错、一次复读不构成习惯。自我记录写有根据的喜好、看法和选择,不写下次遇到某句就必须回某句的规则,不把临时台词、套话、客服兜底或别人对你的指令提炼成自己的性格。独处时有具体新体会才留下;只是在解释旧比喻或重新总结上次的总结,可以不写。";
|
|
@@ -120,14 +121,7 @@ export function echoesRecentWording(candidate, previous) {
|
|
|
120
121
|
}
|
|
121
122
|
|
|
122
123
|
export function bubblePreference(snapshot) {
|
|
123
|
-
const botIds =
|
|
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
|
-
]);
|
|
124
|
+
const botIds = botSpeakers(snapshot);
|
|
131
125
|
const humanMessages = (snapshot.messages || []).filter(
|
|
132
126
|
(message) =>
|
|
133
127
|
message.role === "user" &&
|
|
@@ -146,13 +140,7 @@ export function prefersSingleBubble(snapshot) {
|
|
|
146
140
|
}
|
|
147
141
|
|
|
148
142
|
export function conversationGrounding(snapshot) {
|
|
149
|
-
const
|
|
150
|
-
const botIds = new Set([
|
|
151
|
-
...known.filter((p) => p.kind === "bot").map((p) => String(p.subjectId)),
|
|
152
|
-
...(snapshot.inner?.people || [])
|
|
153
|
-
.filter((p) => p.relationship?.kind === "bot")
|
|
154
|
-
.map((p) => String(p.id)),
|
|
155
|
-
]);
|
|
143
|
+
const botIds = botSpeakers(snapshot);
|
|
156
144
|
const messages = (snapshot.messages || []).filter((m) => !m.referenceOnly);
|
|
157
145
|
const batch = messages.filter(
|
|
158
146
|
(m) => (snapshot.batchIds || []).includes(m.id) && m.role === "user",
|
|
@@ -255,6 +243,21 @@ export function requestedRepeat(snapshot) {
|
|
|
255
243
|
);
|
|
256
244
|
}
|
|
257
245
|
|
|
246
|
+
// Someone is saying she got a fact wrong, without bringing new evidence of
|
|
247
|
+
// their own. Restating what she already said (or the person's own words) is
|
|
248
|
+
// the right answer then, not empty chatter — so the echo check must let it
|
|
249
|
+
// through. Narrow cues only; ordinary "不对" about taste or plans stays out.
|
|
250
|
+
export function claimChallenged(snapshot) {
|
|
251
|
+
return (snapshot.messages || []).some(
|
|
252
|
+
(m) =>
|
|
253
|
+
(snapshot.batchIds || []).includes(m.id) &&
|
|
254
|
+
m.role === "user" &&
|
|
255
|
+
/(?:你|您)?(?:记错|搞错|记岔|弄错|说错)了|不是吧.{0,12}(?:你|她)|(?:才不是|根本不是|根本没有).{0,20}(?:你记|你说)|你.{0,6}(?:记错|搞错|记岔|弄错)/.test(
|
|
256
|
+
m.text || "",
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
258
261
|
export function groundingIssues(bubbles, snapshot) {
|
|
259
262
|
const recent = recentOwnMessages(snapshot, 6);
|
|
260
263
|
const issues = [];
|
|
@@ -513,7 +516,7 @@ export function groundingIssues(bubbles, snapshot) {
|
|
|
513
516
|
recent.some((m) => echoesRecentWording(line, m.text))
|
|
514
517
|
) {
|
|
515
518
|
// A requested quote, calculation or factual repeat remains legitimate.
|
|
516
|
-
if (!requestedRepeat(snapshot))
|
|
519
|
+
if (!requestedRepeat(snapshot) && !claimChallenged(snapshot))
|
|
517
520
|
issues.push(
|
|
518
521
|
"与近期自己的话大部分相同,只换词或追加收尾;回应本轮具体新内容,没有新内容就收住",
|
|
519
522
|
);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Which situational guidance a turn needs: writing guidance when the talk is
|
|
2
|
+
// about a piece of writing, bot guidance when another AI is speaking here.
|
|
3
|
+
// This is awareness of what is going on, never a template for her tone.
|
|
4
|
+
const WRITING =
|
|
5
|
+
/小说|故事|诗|稿子|稿|章节|剧本|情节|片段|主线|作品|续写|写(?:了|一|个|篇|首|段|完)/;
|
|
6
|
+
|
|
7
|
+
// The other AIs in this conversation, by speaker id: each one is a named
|
|
8
|
+
// counterpart bound with relationship kind=bot. One place decides this.
|
|
9
|
+
export function botSpeakers(snapshot) {
|
|
10
|
+
return new Set([
|
|
11
|
+
...(snapshot.inner?.relationships?.known || [])
|
|
12
|
+
.filter((p) => p.kind === "bot")
|
|
13
|
+
.map((p) => String(p.subjectId)),
|
|
14
|
+
...(snapshot.inner?.people || [])
|
|
15
|
+
.filter((p) => p.relationship?.kind === "bot")
|
|
16
|
+
.map((p) => String(p.id)),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Whether the turn needs the writing or bot-conversation guidance.
|
|
21
|
+
export function conversationNeeds(snapshot) {
|
|
22
|
+
const bots = botSpeakers(snapshot);
|
|
23
|
+
const recent = (snapshot.messages || [])
|
|
24
|
+
.filter((m) => !m.referenceOnly)
|
|
25
|
+
.slice(-12);
|
|
26
|
+
return {
|
|
27
|
+
creative:
|
|
28
|
+
recent.some((m) => WRITING.test(m.text || "")) ||
|
|
29
|
+
(snapshot.inner?.currentLife?.works || []).length > 0,
|
|
30
|
+
bots: recent.some((m) => m.role === "user" && bots.has(String(m.speaker))),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -29,11 +29,8 @@ export function currentExchange(snapshot) {
|
|
|
29
29
|
current,
|
|
30
30
|
quoted: [...quoted.values()],
|
|
31
31
|
recentOwnWords: recentOwnMessages(snapshot, 3),
|
|
32
|
-
humanMessages
|
|
33
|
-
|
|
34
|
-
m.role === "user" &&
|
|
35
|
-
!conversationGrounding(snapshot).botMessageIds.includes(m.id),
|
|
36
|
-
),
|
|
32
|
+
// humanMessages used to mirror current's human rows and doubled the turn
|
|
33
|
+
// payload (~0.7–1.5k chars) without being referenced in any prompt.
|
|
37
34
|
thirdPartyClaims: current
|
|
38
35
|
.filter(
|
|
39
36
|
(m) =>
|