luckytri 1.0.8 → 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 +1 -1
- package/README.md +1 -1
- package/docs/en/CHANGELOG.md +35 -24
- package/docs/en/guide.md +3 -1
- package/docs/zh/CHANGELOG.md +35 -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 +0 -8
- package/server/core/conversation-grounding.js +247 -4
- package/server/core/dialogue-context.js +16 -13
- package/server/core/dialogue-contract.js +33 -9
- package/server/core/initiative-context.js +36 -16
- package/server/core/model-json.js +36 -3
- package/server/core/model-manager.js +2 -2
- package/server/core/model-presets.js +169 -24
- package/server/core/persona-manager.js +8 -3
- package/server/core/reader-check.js +130 -26
- package/server/core/reply-delivery.js +152 -11
- package/server/core/repository.js +33 -0
- package/server/core/response-generator.js +12 -3
- package/server/core/response-validator.js +18 -3
- package/server/core/turn-processor.js +26 -4
- package/server/core/turn.js +258 -44
- package/server/i18n/decisions.js +16 -0
- package/server/i18n/errors.js +2 -0
- package/server/i18n/models.js +11 -2
- package/server/mind/expression-grounding.js +5 -2
- package/server/mind/time/activity-recall.js +65 -0
- package/server/mind/time/index.js +6 -2
- package/server/mind/time/tasks.js +8 -10
- package/server/mind/time/works.js +3 -2
- package/server/mind/view.js +9 -2
package/server/core/turn.js
CHANGED
|
@@ -1,15 +1,37 @@
|
|
|
1
1
|
import { replyFocus } from "./conversation-cues.js";
|
|
2
2
|
import { INITIATIVE_PROMPT } from "../mind/initiative.js";
|
|
3
3
|
import { initiativeContext } from "./initiative-context.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
conversationGrounding,
|
|
6
|
+
prefersSingleBubble,
|
|
7
|
+
} from "./conversation-grounding.js";
|
|
5
8
|
import {
|
|
6
9
|
currentExchange,
|
|
7
10
|
dialogueContext,
|
|
8
11
|
explicitStop,
|
|
9
12
|
} from "./dialogue-context.js";
|
|
10
13
|
import { readerQuestions } from "./reader-check.js";
|
|
14
|
+
import {
|
|
15
|
+
botExchange,
|
|
16
|
+
CONTRIBUTION_CHECK,
|
|
17
|
+
CONTRIBUTION_REPAIR,
|
|
18
|
+
} from "./contribution-check.js";
|
|
19
|
+
import {
|
|
20
|
+
claimsLivedAction,
|
|
21
|
+
EXPRESSION_GROUNDING,
|
|
22
|
+
} from "../mind/expression-grounding.js";
|
|
11
23
|
|
|
12
24
|
export const CHOICES = ["speak", "react", "decline", "silent"];
|
|
25
|
+
const INTENT_KINDS = [
|
|
26
|
+
"question",
|
|
27
|
+
"proposal",
|
|
28
|
+
"feeling",
|
|
29
|
+
"sharing",
|
|
30
|
+
"correction",
|
|
31
|
+
"greeting",
|
|
32
|
+
"closing",
|
|
33
|
+
"other",
|
|
34
|
+
];
|
|
13
35
|
const LEGACY = {
|
|
14
36
|
REPLY: "speak",
|
|
15
37
|
MULTI_MESSAGE: "speak",
|
|
@@ -17,8 +39,22 @@ const LEGACY = {
|
|
|
17
39
|
SILENT: "silent",
|
|
18
40
|
};
|
|
19
41
|
|
|
20
|
-
export function maxBubbles(choice) {
|
|
21
|
-
|
|
42
|
+
export function maxBubbles(choice, snapshot) {
|
|
43
|
+
if (choice !== "speak") return 1;
|
|
44
|
+
return prefersSingleBubble(snapshot || {}) ? 1 : 3;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function joinBubbleParts(parts) {
|
|
48
|
+
return parts.reduce((joined, part) => {
|
|
49
|
+
if (!joined) return part;
|
|
50
|
+
if (/^(?:[-*•]|\d+[.)、])\s/u.test(part)) return `${joined}\n${part}`;
|
|
51
|
+
if (/[A-Za-z0-9]$/u.test(joined) && /^[A-Za-z0-9]/u.test(part))
|
|
52
|
+
return `${joined} ${part}`;
|
|
53
|
+
if (/[,、::]$/u.test(joined) || /^[,、;;]/u.test(part))
|
|
54
|
+
return `${joined}${part}`;
|
|
55
|
+
if (/[。!?!?;;…)」』】》]$/u.test(joined)) return `${joined}${part}`;
|
|
56
|
+
return `${joined},${part}`;
|
|
57
|
+
}, "");
|
|
22
58
|
}
|
|
23
59
|
|
|
24
60
|
const ACTION_NAME = /^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]{0,40}$/;
|
|
@@ -73,7 +109,8 @@ export function recallWording(snapshot) {
|
|
|
73
109
|
}
|
|
74
110
|
|
|
75
111
|
// One look at the conversation: what it means to her, how it moves her, and
|
|
76
|
-
// whether and how she answers.
|
|
112
|
+
// whether and how she answers. A bot exchange gets at most one repair pass
|
|
113
|
+
// when its first draft repeats old promises instead of answering new content.
|
|
77
114
|
export async function takeTurn(
|
|
78
115
|
models,
|
|
79
116
|
profile,
|
|
@@ -92,35 +129,179 @@ export async function takeTurn(
|
|
|
92
129
|
: snapshot.unavailableImages?.length
|
|
93
130
|
? "本轮图片没有读取成功,不要编造画面内容。"
|
|
94
131
|
: undefined;
|
|
95
|
-
|
|
96
|
-
profile,
|
|
97
|
-
"turn",
|
|
132
|
+
const turnSystem =
|
|
98
133
|
extra.occasion && ["presence", "outreach"].includes(extra.occasion.type)
|
|
99
134
|
? `${system}\n${INITIATIVE_PROMPT}`
|
|
100
|
-
: system
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
135
|
+
: system;
|
|
136
|
+
const turnInput = {
|
|
137
|
+
context: initiating
|
|
138
|
+
? initiativeContext(snapshot, extra.occasion)
|
|
139
|
+
: dialogueContext(snapshot),
|
|
140
|
+
...(extra.occasion
|
|
141
|
+
? {
|
|
142
|
+
occasion: initiating
|
|
143
|
+
? {
|
|
144
|
+
...extra.occasion,
|
|
145
|
+
initiative: initiativeContext(snapshot, extra.occasion)
|
|
146
|
+
.initiative,
|
|
147
|
+
}
|
|
148
|
+
: extra.occasion,
|
|
149
|
+
}
|
|
150
|
+
: {}),
|
|
151
|
+
...(imageGuide ? { imageGuide } : {}),
|
|
152
|
+
...(extra.pressure ? { pressure: extra.pressure } : {}),
|
|
153
|
+
...(recallWording(snapshot) ? { guidance: recallWording(snapshot) } : {}),
|
|
154
|
+
...(!initiating ? { exchange: currentExchange(snapshot) } : {}),
|
|
155
|
+
};
|
|
156
|
+
let raw = await models.call(
|
|
157
|
+
profile,
|
|
158
|
+
"turn",
|
|
159
|
+
turnSystem,
|
|
160
|
+
turnInput,
|
|
121
161
|
trace,
|
|
122
162
|
images,
|
|
123
163
|
);
|
|
164
|
+
const words = (
|
|
165
|
+
Array.isArray(raw?.bubbles) ? raw.bubbles : [raw?.bubbles]
|
|
166
|
+
).filter((word) => typeof word === "string");
|
|
167
|
+
if (
|
|
168
|
+
snapshot.initiative &&
|
|
169
|
+
raw?.choice === "speak" &&
|
|
170
|
+
claimsLivedAction(words.join("\n"))
|
|
171
|
+
) {
|
|
172
|
+
// Reject a fictional action before experience() can turn its appraisal
|
|
173
|
+
// into a new memory. A self-initiated message has no unanswered request
|
|
174
|
+
// that would justify filling in a different story through rewrites.
|
|
175
|
+
let grounded;
|
|
176
|
+
try {
|
|
177
|
+
grounded = await models.call(
|
|
178
|
+
profile,
|
|
179
|
+
"validation",
|
|
180
|
+
EXPRESSION_GROUNDING,
|
|
181
|
+
{
|
|
182
|
+
candidate: { words },
|
|
183
|
+
currentLife: snapshot.inner?.currentLife || {},
|
|
184
|
+
notes: [],
|
|
185
|
+
self: [],
|
|
186
|
+
},
|
|
187
|
+
trace,
|
|
188
|
+
);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
trace?.steps?.push(`主动经历核对暂不可用,先不分享:${error.message}`);
|
|
191
|
+
}
|
|
192
|
+
if (trace)
|
|
193
|
+
trace.experienceReview = {
|
|
194
|
+
words: [...words],
|
|
195
|
+
ok: grounded?.ok === true,
|
|
196
|
+
reason: grounded?.reason || "实际经历尚未核实",
|
|
197
|
+
};
|
|
198
|
+
if (grounded?.ok !== true)
|
|
199
|
+
return {
|
|
200
|
+
...raw,
|
|
201
|
+
choice: "silent",
|
|
202
|
+
bubbles: [],
|
|
203
|
+
appraisal: "",
|
|
204
|
+
feelings: [],
|
|
205
|
+
bonds: [],
|
|
206
|
+
contribution: {
|
|
207
|
+
kind: "none",
|
|
208
|
+
point: "旧草稿中的新行动尚无实际记录支持",
|
|
209
|
+
},
|
|
210
|
+
reason: "这段念头的经历依据还没有核实,先留在自己这里",
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const exchange = botExchange(snapshot, raw);
|
|
214
|
+
if (
|
|
215
|
+
exchange &&
|
|
216
|
+
(raw.choice === "speak" || raw.choice === "react") &&
|
|
217
|
+
raw.contribution?.kind !== "none"
|
|
218
|
+
) {
|
|
219
|
+
try {
|
|
220
|
+
let review = await models.call(
|
|
221
|
+
profile,
|
|
222
|
+
"validation",
|
|
223
|
+
CONTRIBUTION_CHECK,
|
|
224
|
+
{
|
|
225
|
+
botExchange: exchange,
|
|
226
|
+
reply: raw.bubbles || [],
|
|
227
|
+
plannedPoint: raw.contribution?.point || "",
|
|
228
|
+
},
|
|
229
|
+
trace,
|
|
230
|
+
);
|
|
231
|
+
if (trace && typeof review.hasContribution === "boolean")
|
|
232
|
+
trace.contributionReview = review;
|
|
233
|
+
if (review.hasContribution === false && review.shouldRepair === true) {
|
|
234
|
+
trace?.steps?.push("新问题或新段落被上一稿漏接,按当前内容重新想一次");
|
|
235
|
+
try {
|
|
236
|
+
const repaired = await models.call(
|
|
237
|
+
profile,
|
|
238
|
+
"turn",
|
|
239
|
+
`${turnSystem}\n${CONTRIBUTION_REPAIR}`,
|
|
240
|
+
{
|
|
241
|
+
...turnInput,
|
|
242
|
+
contributionRepair: {
|
|
243
|
+
previousReply: raw.bubbles || [],
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
trace,
|
|
247
|
+
images,
|
|
248
|
+
);
|
|
249
|
+
const repairedExchange = botExchange(snapshot, repaired);
|
|
250
|
+
if (
|
|
251
|
+
repairedExchange &&
|
|
252
|
+
["speak", "react"].includes(repaired.choice) &&
|
|
253
|
+
repaired.contribution?.kind !== "none"
|
|
254
|
+
) {
|
|
255
|
+
const revisedReview = await models.call(
|
|
256
|
+
profile,
|
|
257
|
+
"validation",
|
|
258
|
+
CONTRIBUTION_CHECK,
|
|
259
|
+
{
|
|
260
|
+
botExchange: repairedExchange,
|
|
261
|
+
reply: repaired.bubbles || [],
|
|
262
|
+
plannedPoint: repaired.contribution?.point || "",
|
|
263
|
+
repairAttempt: true,
|
|
264
|
+
},
|
|
265
|
+
trace,
|
|
266
|
+
);
|
|
267
|
+
review = revisedReview;
|
|
268
|
+
}
|
|
269
|
+
if (trace) {
|
|
270
|
+
trace.contributionReview = review;
|
|
271
|
+
trace.contributionRepair = {
|
|
272
|
+
attempted: true,
|
|
273
|
+
succeeded: review.hasContribution === true,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
if (review.hasContribution === true) return repaired;
|
|
277
|
+
} catch (error) {
|
|
278
|
+
trace?.steps?.push(`按新内容重答失败,本轮先不发:${error.message}`);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
...raw,
|
|
282
|
+
choice: "silent",
|
|
283
|
+
bubbles: [],
|
|
284
|
+
feelings: [],
|
|
285
|
+
bonds: [],
|
|
286
|
+
contribution: { kind: "none", point: "新内容没有形成可发送的回复" },
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (review.hasContribution === false)
|
|
290
|
+
return {
|
|
291
|
+
...raw,
|
|
292
|
+
choice: "silent",
|
|
293
|
+
bubbles: [],
|
|
294
|
+
feelings: [],
|
|
295
|
+
bonds: [],
|
|
296
|
+
contribution: { kind: "none", point: "没有新的内容,不再换词确认" },
|
|
297
|
+
};
|
|
298
|
+
} catch (error) {
|
|
299
|
+
trace?.steps?.push(
|
|
300
|
+
`接话内容核对暂不可用,继续原有校验:${error.message}`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return raw;
|
|
124
305
|
}
|
|
125
306
|
|
|
126
307
|
const ids = (value) =>
|
|
@@ -149,7 +330,11 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
149
330
|
}
|
|
150
331
|
if (
|
|
151
332
|
choice === "react" &&
|
|
152
|
-
readerQuestions(snapshot, {
|
|
333
|
+
readerQuestions(snapshot, {
|
|
334
|
+
choice,
|
|
335
|
+
targetMessageIds: targets,
|
|
336
|
+
understanding: raw.understanding,
|
|
337
|
+
}).some((q) => targets.includes(q.id))
|
|
153
338
|
) {
|
|
154
339
|
choice = "speak";
|
|
155
340
|
trace?.steps?.push("对方在要求解释,保留完整回答空间,不按极短反应截断");
|
|
@@ -173,6 +358,20 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
173
358
|
"这是一句完整回应,按正常发言保留,不为短反应标签改写原意",
|
|
174
359
|
);
|
|
175
360
|
}
|
|
361
|
+
const bubbleLimit = maxBubbles(choice, snapshot);
|
|
362
|
+
if (bubbles.length > bubbleLimit) {
|
|
363
|
+
if (choice === "speak" && bubbleLimit === 1) {
|
|
364
|
+
bubbles = [joinBubbleParts(bubbles)];
|
|
365
|
+
trace?.steps?.push("根据对方的偏好,把连续补充合成一条完整消息");
|
|
366
|
+
} else {
|
|
367
|
+
bubbles = bubbles.slice(0, bubbleLimit);
|
|
368
|
+
trace?.steps?.push(
|
|
369
|
+
choice === "react"
|
|
370
|
+
? "简短反应只留第一条气泡"
|
|
371
|
+
: "回复超过当前对方的分段偏好,已收束到允许的条数",
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
176
375
|
if (choice === "silent") bubbles = [];
|
|
177
376
|
const crisisIds = ids(raw.crisis?.messageIds).filter((id) =>
|
|
178
377
|
batchIds.has(id),
|
|
@@ -189,10 +388,13 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
189
388
|
bubbles = [];
|
|
190
389
|
trace?.steps?.push("有人可能处在危机里,底线要求认真回应");
|
|
191
390
|
}
|
|
391
|
+
const grounding = conversationGrounding(snapshot);
|
|
392
|
+
const botFollowupLimit =
|
|
393
|
+
!crisis && !snapshot.initiative && grounding.botFollowupLimit;
|
|
192
394
|
const botLoop =
|
|
193
395
|
!crisis &&
|
|
194
396
|
!snapshot.initiative &&
|
|
195
|
-
(
|
|
397
|
+
(grounding.botLoop ||
|
|
196
398
|
(raw.contribution?.kind === "none" &&
|
|
197
399
|
!raw.act &&
|
|
198
400
|
!raw.share &&
|
|
@@ -204,10 +406,15 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
204
406
|
.every((m) =>
|
|
205
407
|
conversationGrounding(snapshot).botMessageIds.includes(m.id),
|
|
206
408
|
)));
|
|
207
|
-
|
|
409
|
+
const botYield = botLoop || botFollowupLimit;
|
|
410
|
+
if (botYield) {
|
|
208
411
|
choice = "silent";
|
|
209
412
|
bubbles = [];
|
|
210
|
-
trace?.steps?.push(
|
|
413
|
+
trace?.steps?.push(
|
|
414
|
+
botFollowupLimit
|
|
415
|
+
? "机器人之间已经连续说过两轮,这次没有新的直接提问或纠正,先留出空间"
|
|
416
|
+
: "机器人连续确认没有新内容,这段对话在此收住",
|
|
417
|
+
);
|
|
211
418
|
}
|
|
212
419
|
const stopped = !crisis && explicitStop(snapshot);
|
|
213
420
|
if (stopped) {
|
|
@@ -222,7 +429,7 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
222
429
|
return {
|
|
223
430
|
choice,
|
|
224
431
|
share:
|
|
225
|
-
!
|
|
432
|
+
!botYield &&
|
|
226
433
|
!stopped &&
|
|
227
434
|
raw.share &&
|
|
228
435
|
["send", "later", "decline"].includes(raw.share.choice)
|
|
@@ -233,7 +440,7 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
233
440
|
}
|
|
234
441
|
: null,
|
|
235
442
|
attention:
|
|
236
|
-
!
|
|
443
|
+
!botYield &&
|
|
237
444
|
raw.attention &&
|
|
238
445
|
["continue", "chat", "rest"].includes(raw.attention.action)
|
|
239
446
|
? {
|
|
@@ -241,16 +448,23 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
241
448
|
reason: text(raw.attention.reason, 120),
|
|
242
449
|
}
|
|
243
450
|
: { action: "continue" },
|
|
244
|
-
appraisal:
|
|
245
|
-
?
|
|
451
|
+
appraisal: botYield
|
|
452
|
+
? botFollowupLimit
|
|
453
|
+
? "这段互答已经有来有回,先给聊天留点空间。"
|
|
454
|
+
: "对方在确认先前的话,没有新的内容。"
|
|
246
455
|
: text(raw.appraisal, 200),
|
|
247
|
-
reason:
|
|
248
|
-
?
|
|
456
|
+
reason: botYield
|
|
457
|
+
? botFollowupLimit
|
|
458
|
+
? "连续互答两轮,眼下没有直接问题或纠正,先给群里留点空间。"
|
|
459
|
+
: "这段交流已经结束,先收住。"
|
|
249
460
|
: text(raw.reason || raw.appraisal, 300) || "此刻的判断",
|
|
250
|
-
topic:
|
|
461
|
+
topic: botYield ? "暂时停一下" : text(raw.topic, 40),
|
|
251
462
|
...(raw.understanding && typeof raw.understanding === "object"
|
|
252
463
|
? {
|
|
253
464
|
understanding: {
|
|
465
|
+
...(INTENT_KINDS.includes(raw.understanding.kind)
|
|
466
|
+
? { kind: raw.understanding.kind }
|
|
467
|
+
: {}),
|
|
254
468
|
messageIds: ids(raw.understanding.messageIds).filter((id) =>
|
|
255
469
|
batchIds.has(id),
|
|
256
470
|
),
|
|
@@ -275,17 +489,17 @@ export function normalizeTurn(raw, snapshot, trace) {
|
|
|
275
489
|
),
|
|
276
490
|
],
|
|
277
491
|
evidenceIds: ids(raw.evidenceIds).filter((id) => known.has(id)),
|
|
278
|
-
feelings: (Array.isArray(raw.feelings) && !
|
|
492
|
+
feelings: (Array.isArray(raw.feelings) && !botYield ? raw.feelings : [])
|
|
279
493
|
.filter((f) => f && typeof f.feeling === "string")
|
|
280
494
|
.slice(0, 3),
|
|
281
|
-
bonds: (Array.isArray(raw.bonds) && !
|
|
495
|
+
bonds: (Array.isArray(raw.bonds) && !botYield ? raw.bonds : [])
|
|
282
496
|
.filter((b) => b && b.userId != null && typeof b.change === "string")
|
|
283
497
|
.map((b) => ({ ...b, evidence: ids(b.evidence) }))
|
|
284
498
|
.slice(0, 4),
|
|
285
499
|
crisis: crisis ? { clear: true, messageIds: crisisIds } : { clear: false },
|
|
286
|
-
act:
|
|
287
|
-
bubbles: bubbles.slice(0,
|
|
288
|
-
maxBubbles:
|
|
500
|
+
act: botYield || stopped ? null : normalizeAct(raw.act, trace),
|
|
501
|
+
bubbles: bubbles.slice(0, bubbleLimit),
|
|
502
|
+
maxBubbles: bubbleLimit,
|
|
289
503
|
};
|
|
290
504
|
}
|
|
291
505
|
|
package/server/i18n/decisions.js
CHANGED
|
@@ -27,6 +27,10 @@ export const DECISIONS = {
|
|
|
27
27
|
"Context compression failed; it will retry automatically",
|
|
28
28
|
|
|
29
29
|
// orchestrator, delivery
|
|
30
|
+
"删去未通过检查的多余末尾气泡,保留已通过全部校验的原答复":
|
|
31
|
+
"Removed the rejected trailing bubbles and kept the original answer that passed every check",
|
|
32
|
+
"这段念头的经历依据还没有核实,先留在自己这里":
|
|
33
|
+
"The experience behind this thought is not verified yet; keeping it private for now",
|
|
30
34
|
"有一个备用模型档案不存在,已跳过":
|
|
31
35
|
"A backup model profile does not exist; skipped",
|
|
32
36
|
"引用消息未能恢复,保留 unknown,不认领对象":
|
|
@@ -173,6 +177,18 @@ export const DECISIONS = {
|
|
|
173
177
|
};
|
|
174
178
|
|
|
175
179
|
export const DECISION_PATTERNS = [
|
|
180
|
+
[
|
|
181
|
+
"主动经历核对暂不可用,先不分享:{0}",
|
|
182
|
+
"The proactive experience check is unavailable; holding the message: {0}",
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
"接话内容核对暂不可用,继续原有校验:{0}",
|
|
186
|
+
"The contribution check is unavailable; continuing the existing checks: {0}",
|
|
187
|
+
],
|
|
188
|
+
[
|
|
189
|
+
"主动消息声称的新行动尚无实际活动记录支持:{0}",
|
|
190
|
+
"The new action claimed by this proactive message lacks activity records: {0}",
|
|
191
|
+
],
|
|
176
192
|
["整理了 {0} 段语境摘要", "Sorted {0} context summaries"],
|
|
177
193
|
["QQ 用户·{0}", "QQ user·{0}"],
|
|
178
194
|
["读取 QQ {0} 失败:{1}", "Reading QQ {0} failed: {1}"],
|
package/server/i18n/errors.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// key, exactly as the code writes it. `{0}` stands for a value the code puts
|
|
3
3
|
// into the message; patterns are used when a message carries one.
|
|
4
4
|
export const ERRORS = {
|
|
5
|
+
"JSON 同一对象含重复字段,不能确定哪份内容有效":
|
|
6
|
+
"A JSON object contains duplicate fields; the intended value is ambiguous",
|
|
5
7
|
"写日记期间关系记录已变化,本次旧稿未保存,下次按新认识整理":
|
|
6
8
|
"Relationships changed while writing the diary; the old draft was not saved and the next attempt will use the new knowledge",
|
|
7
9
|
"写回顾期间关系记录已变化,本次旧稿未保存,下次按新认识整理":
|
package/server/i18n/models.js
CHANGED
|
@@ -23,6 +23,7 @@ export const MODEL_LABELS = {
|
|
|
23
23
|
|
|
24
24
|
// providers and labels
|
|
25
25
|
火山方舟: "Volcengine Ark",
|
|
26
|
+
"火山方舟 Agent Plan": "Volcengine Ark Agent Plan",
|
|
26
27
|
"火山方舟 Coding Plan": "Volcengine Ark Coding Plan",
|
|
27
28
|
"方舟自动路由(ark-code-latest)": "Ark auto-routing (ark-code-latest)",
|
|
28
29
|
"GLM 最新别名(glm-latest)": "GLM latest alias (glm-latest)",
|
|
@@ -37,6 +38,8 @@ export const MODEL_LABELS = {
|
|
|
37
38
|
// notes
|
|
38
39
|
"控制台管理的动态模型入口;实际使用哪一个模型由 Coding Plan 控制台选择,切换通常数分钟后生效。支持图片理解。":
|
|
39
40
|
"A dynamic model entry managed in the console; which model is actually used is chosen in the Coding Plan console, and a switch usually takes a few minutes. Supports image understanding.",
|
|
41
|
+
"Agent Plan 自动路由入口;由套餐配置动态选择可用语言模型,支持图片理解。":
|
|
42
|
+
"Agent Plan auto-routing entry; the plan dynamically selects an available language model. Supports image understanding.",
|
|
40
43
|
[`当前主力。思考默认开启(high),可关闭;支持看图。${FLAT_TAIL}`]: `The current mainstay. Thinking is on by default (high) and can be turned off; supports images. ${FLAT_TAIL_EN}`,
|
|
41
44
|
[`V4-Pro-0813。思考默认开启(high),可关闭;仅文本。${FLAT_TAIL}`]: `V4-Pro-0813. Thinking is on by default (high) and can be turned off; text only. ${FLAT_TAIL_EN}`,
|
|
42
45
|
[`旗舰。思考默认开启,可关闭;支持图片、视频、音频输入。${FLAT_TAIL}`]: `Flagship. Thinking is on by default and can be turned off; accepts image, video and audio input. ${FLAT_TAIL_EN}`,
|
|
@@ -87,8 +90,6 @@ export const MODEL_LABELS = {
|
|
|
87
90
|
"OpenCode Zen · Qwen lightweight multimodal · Anthropic Messages API.",
|
|
88
91
|
"OpenCode Zen · MiniMax 主力模型 · Chat Completions 接口。":
|
|
89
92
|
"OpenCode Zen · MiniMax main model · Chat Completions API.",
|
|
90
|
-
"OpenCode Zen · 当前限时免费 · Chat Completions 接口。参考 OpenRouter Space Bunny Alpha 规格:1M 上下文、最高 524K 输出,支持图像/视频、推理、工具和 JSON;其匿名第三方提供方可能保留提示和回答。LuckyTri 默认输入 128K、输出 8K、低推理;当前图片链路可用,视频链路未支持。Zen 路由能力与数据政策可能不同。":
|
|
91
|
-
"OpenCode Zen · free for a limited time · Chat Completions API. Modeled on the OpenRouter Space Bunny Alpha spec: 1M context, up to 524K output, image/video, reasoning, tools and JSON; its anonymous third-party providers may keep prompts and replies. LuckyTri defaults to 128K input, 8K output and low reasoning; images work today, video is not supported. Zen routes may differ in ability and data policy.",
|
|
92
93
|
"OpenAI 兼容接口。上下文先按 128K 填,思考档可按供应商再改。":
|
|
93
94
|
"OpenAI-compatible API. Context is prefilled at 128K; the thinking level can be changed per provider.",
|
|
94
95
|
"已填好 OpenRouter API 地址。请自行填写模型 ID、API Key,并按所选模型设置上下文、输出和能力参数。":
|
|
@@ -100,6 +101,14 @@ export const MODEL_PATTERNS = [
|
|
|
100
101
|
"OpenCode Go · {0} · {1} 接口。默认上下文和输出预算为保守起始值,可按账户实际模型上限调整。",
|
|
101
102
|
"OpenCode Go · {0} · {1} API. Default context and output budgets are conservative starting values; adjust them to your account's real model limits.",
|
|
102
103
|
],
|
|
104
|
+
[
|
|
105
|
+
"Agent Plan 专属 Responses 接口 · 上下文 {0} · LuckyTri 输出预算 {1} · 支持图片理解。默认高强度推理;采样参数由模型服务端处理。",
|
|
106
|
+
"Agent Plan Responses API · context {0} · LuckyTri output budget {1} · supports image understanding. High reasoning by default; sampling parameters are handled by the model service.",
|
|
107
|
+
],
|
|
108
|
+
[
|
|
109
|
+
"Agent Plan 专属 Responses 接口 · 上下文 {0} · LuckyTri 输出预算 {1} · 文本模型。默认高强度推理;采样参数由模型服务端处理。",
|
|
110
|
+
"Agent Plan Responses API · context {0} · LuckyTri output budget {1} · text model. High reasoning by default; sampling parameters are handled by the model service.",
|
|
111
|
+
],
|
|
103
112
|
[
|
|
104
113
|
"Coding Plan 专属 Responses 接口 · 上下文 {0} · 官方最大输出 {1} · 支持图片理解。默认中等推理;Kimi K2.8 Preview 在 LuckyTri 中预留 64K 输出预算以保留长上下文。",
|
|
105
114
|
"Coding Plan–only Responses API · context {0} · vendor max output {1} · supports image understanding. Medium reasoning by default; Kimi K2.8 Preview reserves a 64K output budget in LuckyTri to keep the long context.",
|
|
@@ -4,9 +4,12 @@ export function claimsLivedAction(content) {
|
|
|
4
4
|
.some(
|
|
5
5
|
(clause) =>
|
|
6
6
|
!/想|打算|准备|希望|假如|设想|如果/.test(clause) &&
|
|
7
|
-
/(?:刚才|刚刚|今天|这次|然后|真的|已经)[^。!?]{0,25}(
|
|
7
|
+
(/(?:刚才|刚刚|今天|这次|然后|真的|已经)[^。!?]{0,25}(?:读了|读上|读完|读[一二两三几\d]+[段页章节遍]|坐下来读|翻开|翻出来|写完|完成了|打开了|开始读|动手了)/.test(
|
|
8
8
|
clause,
|
|
9
|
-
)
|
|
9
|
+
) ||
|
|
10
|
+
/我(?:自己)?[^。!?]{0,10}(?:读上了|开始读了|翻开了|完成了|写完了)/.test(
|
|
11
|
+
clause,
|
|
12
|
+
)),
|
|
10
13
|
);
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const OWN_REFERENCE =
|
|
2
|
+
/你.{0,16}(?:不是说|明明说|说过|提过|答应过)|你跟.{0,16}(?:说|提过|答应过)|(?:上次|之前|刚才|昨天).{0,18}(?:你|我).{0,10}(?:说|提|答应)/;
|
|
3
|
+
|
|
4
|
+
// A same-person follow-up may point to what she just said without repeating
|
|
5
|
+
// a work's title. Use only replies actually addressed to this speaker as extra
|
|
6
|
+
// search terms; the private reply itself never enters the memory result.
|
|
7
|
+
export function activityRecallCue(
|
|
8
|
+
batch = [],
|
|
9
|
+
messages = [],
|
|
10
|
+
{ excludedSpeakers = new Set() } = {},
|
|
11
|
+
) {
|
|
12
|
+
const current = batch.filter(
|
|
13
|
+
(message) =>
|
|
14
|
+
message.role === "user" &&
|
|
15
|
+
!excludedSpeakers.has(String(message.speaker ?? message.userId ?? "")) &&
|
|
16
|
+
OWN_REFERENCE.test(message.text || ""),
|
|
17
|
+
);
|
|
18
|
+
if (!current.length) return batch;
|
|
19
|
+
|
|
20
|
+
const speakers = new Set(
|
|
21
|
+
current
|
|
22
|
+
.map((message) => String(message.speaker ?? message.userId ?? ""))
|
|
23
|
+
.filter(Boolean),
|
|
24
|
+
);
|
|
25
|
+
if (!speakers.size) return batch;
|
|
26
|
+
const byId = new Map(
|
|
27
|
+
messages.map((message) => [Number(message.seq ?? message.id), message]),
|
|
28
|
+
);
|
|
29
|
+
const cutoff = Math.min(
|
|
30
|
+
...batch
|
|
31
|
+
.map((message) => Number(message.id ?? message.seq))
|
|
32
|
+
.filter(Number.isFinite),
|
|
33
|
+
);
|
|
34
|
+
const now = Math.max(
|
|
35
|
+
...batch.map((message) => Number(message.time)).filter(Number.isFinite),
|
|
36
|
+
);
|
|
37
|
+
const replies = messages.filter((message) => {
|
|
38
|
+
if (message.role !== "assistant" || message.referenceOnly) return false;
|
|
39
|
+
if (Number.isFinite(message.id ?? message.seq) && Number.isFinite(cutoff)) {
|
|
40
|
+
const id = Number(message.id ?? message.seq);
|
|
41
|
+
if (
|
|
42
|
+
id >= cutoff &&
|
|
43
|
+
(!Number.isFinite(message.time) || message.time > now)
|
|
44
|
+
)
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const targets = [
|
|
48
|
+
...(Array.isArray(message.replyTargets) ? message.replyTargets : []),
|
|
49
|
+
...(Array.isArray(message.replyTargetIds)
|
|
50
|
+
? message.replyTargetIds.map((id) => byId.get(Number(id)))
|
|
51
|
+
: []),
|
|
52
|
+
];
|
|
53
|
+
return targets.some((target) =>
|
|
54
|
+
speakers.has(String(target?.speaker ?? target?.userId ?? "")),
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
return [
|
|
58
|
+
...batch,
|
|
59
|
+
...replies.slice(-2).map((message) => ({
|
|
60
|
+
role: "assistant",
|
|
61
|
+
activityEvidence: true,
|
|
62
|
+
text: message.text || "",
|
|
63
|
+
})),
|
|
64
|
+
];
|
|
65
|
+
}
|
|
@@ -422,7 +422,7 @@ export class TimeSystem {
|
|
|
422
422
|
}),
|
|
423
423
|
};
|
|
424
424
|
}
|
|
425
|
-
view({ session, now = this.now(), cue = [] } = {}) {
|
|
425
|
+
view({ session, now = this.now(), cue = [], activityCue = cue } = {}) {
|
|
426
426
|
const primary = this.primary(),
|
|
427
427
|
task = primary?.created <= now ? primary : null;
|
|
428
428
|
let current =
|
|
@@ -548,7 +548,11 @@ export class TimeSystem {
|
|
|
548
548
|
plannedFor: this.planView(row),
|
|
549
549
|
})),
|
|
550
550
|
works: this.works.fragments({ session, now, cue }),
|
|
551
|
-
activityRecall: this.works.activityEvidence({
|
|
551
|
+
activityRecall: this.works.activityEvidence({
|
|
552
|
+
session,
|
|
553
|
+
now,
|
|
554
|
+
cue: activityCue,
|
|
555
|
+
}),
|
|
552
556
|
...(() => {
|
|
553
557
|
const actions = this.db
|
|
554
558
|
.prepare(
|
|
@@ -212,16 +212,14 @@ export class Tasks {
|
|
|
212
212
|
now,
|
|
213
213
|
);
|
|
214
214
|
if (type === "read" && awaitsTextInput(title)) {
|
|
215
|
-
this.db
|
|
216
|
-
.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
id,
|
|
224
|
-
);
|
|
215
|
+
this.db.prepare("UPDATE mind_time_tasks SET checkpoint=? WHERE id=?").run(
|
|
216
|
+
JSON.stringify({
|
|
217
|
+
awaitedText: true,
|
|
218
|
+
expectedTextTitle: intent.topic,
|
|
219
|
+
awaitedAfter: now,
|
|
220
|
+
}),
|
|
221
|
+
id,
|
|
222
|
+
);
|
|
225
223
|
this.wait(
|
|
226
224
|
this.get(id),
|
|
227
225
|
"等待约定的正文或资料",
|
|
@@ -464,7 +464,7 @@ export class Works {
|
|
|
464
464
|
(Array.isArray(cue) ? cue : [cue]).map((v) =>
|
|
465
465
|
typeof v === "string"
|
|
466
466
|
? v
|
|
467
|
-
: v?.role === "assistant"
|
|
467
|
+
: v?.role === "assistant" && !v.activityEvidence
|
|
468
468
|
? ""
|
|
469
469
|
: v?.text || "",
|
|
470
470
|
),
|
|
@@ -472,7 +472,8 @@ export class Works {
|
|
|
472
472
|
if (!terms.size) return [];
|
|
473
473
|
// A private conversation can motivate her own reading. Its contents and
|
|
474
474
|
// promises stay private; absence from the public fragments must not erase
|
|
475
|
-
// the fact that the
|
|
475
|
+
// the fact that the reading took place. Search may also use a same-speaker
|
|
476
|
+
// reply delivered in this room to resolve an implicit follow-up.
|
|
476
477
|
return this.db
|
|
477
478
|
.prepare(
|
|
478
479
|
`SELECT w.id,w.title,w.discretion,w.session_id,p.kind,p.title project_title,p.bible,v.created,v.provenance,v.sources
|
package/server/mind/view.js
CHANGED
|
@@ -95,7 +95,14 @@ function feedback(mind, session, now) {
|
|
|
95
95
|
// so anything this batch of messages brings back to mind belongs there.
|
|
96
96
|
export function innerView(
|
|
97
97
|
mind,
|
|
98
|
-
{
|
|
98
|
+
{
|
|
99
|
+
session,
|
|
100
|
+
kind = "group",
|
|
101
|
+
people = [],
|
|
102
|
+
cue = [],
|
|
103
|
+
activityCue = cue,
|
|
104
|
+
now = Date.now(),
|
|
105
|
+
},
|
|
99
106
|
) {
|
|
100
107
|
const nature = mind.nature.current(now);
|
|
101
108
|
const affect = mind.affect.state(now, { nature, room: session });
|
|
@@ -300,7 +307,7 @@ export function innerView(
|
|
|
300
307
|
self,
|
|
301
308
|
affect: cause ? affect : { ...affect, cause: "" },
|
|
302
309
|
inner: {
|
|
303
|
-
currentLife: mind.time.view({ session, now, cue }),
|
|
310
|
+
currentLife: mind.time.view({ session, now, cue, activityCue }),
|
|
304
311
|
state: `${affect.phaseLabel},精力${affect.energyLabel},心情${affect.mood}${cause ? `(${cause})` : ""}${affect.lately ? `,${affect.lately}` : ""}`,
|
|
305
312
|
...(persons.length ? { people: persons } : {}),
|
|
306
313
|
...(relationships.known.length ? { relationships } : {}),
|