luckytri 1.0.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/.github/SECURITY.md +71 -0
- package/LICENSE +21 -0
- package/README.en.md +250 -0
- package/README.md +250 -0
- package/bin/luckytri.js +107 -0
- package/docs/brand/banner.png +0 -0
- package/docs/brand/palette.png +0 -0
- package/docs/brand/portrait.png +0 -0
- package/docs/en/CHANGELOG.md +113 -0
- package/docs/en/architecture.md +152 -0
- package/docs/en/chat-style.md +62 -0
- package/docs/en/connect.md +63 -0
- package/docs/en/guide.md +216 -0
- package/docs/en/her-life.md +189 -0
- package/docs/en/npm-release.md +25 -0
- package/docs/en/plugins.md +82 -0
- package/docs/en/recovery.md +28 -0
- package/docs/en/time.md +71 -0
- package/docs/en/webui.md +73 -0
- package/docs/zh/CHANGELOG.md +113 -0
- package/docs/zh/architecture.md +152 -0
- package/docs/zh/chat-style.md +62 -0
- package/docs/zh/connect.md +63 -0
- package/docs/zh/guide.md +216 -0
- package/docs/zh/her-life.md +189 -0
- package/docs/zh/npm-release.md +33 -0
- package/docs/zh/plugins.md +82 -0
- package/docs/zh/recovery.md +28 -0
- package/docs/zh/time.md +71 -0
- package/docs/zh/webui.md +73 -0
- package/package.json +108 -0
- package/plugins/sdk/index.d.ts +155 -0
- package/plugins/sdk/testing.js +58 -0
- package/plugins/weather/index.js +82 -0
- package/plugins/weather/luckytri-plugin.json +29 -0
- package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
- package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
- package/public/app/assets/Empty-CpP5kQHH.js +2 -0
- package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
- package/public/app/assets/Field-D4gY-INU.js +1 -0
- package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
- package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
- package/public/app/assets/LifePage-BUi5euBF.css +1 -0
- package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
- package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
- package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
- package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
- package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
- package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
- package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
- package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
- package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
- package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
- package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
- package/public/app/assets/TimePage-BOms_n4I.css +1 -0
- package/public/app/assets/TimePage-Cw44inM4.js +3 -0
- package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
- package/public/app/assets/index-BheRW-1t.js +4 -0
- package/public/app/assets/index-YDz1ree1.css +1 -0
- package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
- package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
- package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
- package/public/app/assets/media-BzPDBm7e.js +1 -0
- package/public/app/index.html +27 -0
- package/public/guide.en.html +1053 -0
- package/public/guide.html +893 -0
- package/public/plugin-kit/v1/bridge.js +30 -0
- package/public/plugin-kit/v1/kit.css +14 -0
- package/public/tutorial.en.md +214 -0
- package/public/tutorial.md +214 -0
- package/scripts/backup.js +311 -0
- package/scripts/browser.js +41 -0
- package/scripts/launch.js +94 -0
- package/scripts/plugin.js +150 -0
- package/scripts/proxy-config.js +167 -0
- package/scripts/recover.js +26 -0
- package/scripts/runtime.js +25 -0
- package/scripts/service.js +47 -0
- package/scripts/setup.js +50 -0
- package/scripts/start-server.js +41 -0
- package/scripts/stop.js +36 -0
- package/server/app.js +96 -0
- package/server/channels/adapters.js +27 -0
- package/server/channels/contract.js +57 -0
- package/server/channels/hub.js +165 -0
- package/server/channels/index.js +25 -0
- package/server/channels/onebot/adapter.js +288 -0
- package/server/channels/onebot/index.js +336 -0
- package/server/channels/onebot/token.js +3 -0
- package/server/channels/qqbot/adapter.js +253 -0
- package/server/channels/qqbot/api.js +199 -0
- package/server/channels/qqbot/credentials.js +11 -0
- package/server/channels/qqbot/gateway.js +274 -0
- package/server/channels/qqbot/index.js +389 -0
- package/server/channels/segments.js +40 -0
- package/server/channels/session-key.js +153 -0
- package/server/channels/state.js +42 -0
- package/server/core/api.js +564 -0
- package/server/core/capabilities.js +208 -0
- package/server/core/context-builder.js +288 -0
- package/server/core/context-compactor.js +454 -0
- package/server/core/conversation-cues.js +325 -0
- package/server/core/conversation-manager.js +87 -0
- package/server/core/events.js +60 -0
- package/server/core/feedback.js +8 -0
- package/server/core/group-style.js +72 -0
- package/server/core/image-preprocessor.js +208 -0
- package/server/core/initiative-context.js +66 -0
- package/server/core/input-budget.js +32 -0
- package/server/core/local-demo.js +98 -0
- package/server/core/message-manager.js +118 -0
- package/server/core/message-scheduler.js +78 -0
- package/server/core/model-json.js +190 -0
- package/server/core/model-manager.js +1213 -0
- package/server/core/model-presets.js +900 -0
- package/server/core/name-call.js +29 -0
- package/server/core/net-guard.js +49 -0
- package/server/core/network.js +157 -0
- package/server/core/orchestrator.js +569 -0
- package/server/core/perceivers.js +91 -0
- package/server/core/person-name.js +22 -0
- package/server/core/persona-manager.js +243 -0
- package/server/core/reply-delivery.js +393 -0
- package/server/core/reply-target-resolver.js +50 -0
- package/server/core/repository.js +293 -0
- package/server/core/response-generator.js +50 -0
- package/server/core/response-validator.js +508 -0
- package/server/core/retired-prompts.js +51 -0
- package/server/core/sessions.js +109 -0
- package/server/core/speaker-names.js +92 -0
- package/server/core/topic-tracker.js +65 -0
- package/server/core/turn-processor.js +630 -0
- package/server/core/turn.js +236 -0
- package/server/core/vision-manager.js +633 -0
- package/server/http.js +18 -0
- package/server/i18n/decisions.js +206 -0
- package/server/i18n/errors.js +497 -0
- package/server/i18n/index.js +144 -0
- package/server/i18n/labels.js +653 -0
- package/server/i18n/models.js +111 -0
- package/server/index.js +102 -0
- package/server/knowledge/api.js +342 -0
- package/server/knowledge/embedding-profile.js +114 -0
- package/server/knowledge/index.js +13 -0
- package/server/knowledge/manager.js +386 -0
- package/server/knowledge/retrieval.js +93 -0
- package/server/knowledge/schema.js +154 -0
- package/server/knowledge/vector-index.js +90 -0
- package/server/mind/affect.js +265 -0
- package/server/mind/anticipations.js +507 -0
- package/server/mind/api.js +703 -0
- package/server/mind/attention.js +200 -0
- package/server/mind/bonds.js +558 -0
- package/server/mind/budget.js +152 -0
- package/server/mind/clock.js +83 -0
- package/server/mind/context-selection.js +61 -0
- package/server/mind/continuity.js +562 -0
- package/server/mind/conversation-origin.js +175 -0
- package/server/mind/day-planner.js +324 -0
- package/server/mind/days.js +210 -0
- package/server/mind/evidence-read.js +45 -0
- package/server/mind/evidence.js +148 -0
- package/server/mind/faces.js +138 -0
- package/server/mind/guard.js +60 -0
- package/server/mind/index.js +445 -0
- package/server/mind/initiative.js +333 -0
- package/server/mind/life/activities.js +21 -0
- package/server/mind/life/context.js +309 -0
- package/server/mind/life/diary.js +786 -0
- package/server/mind/life/growth.js +252 -0
- package/server/mind/life/index.js +1049 -0
- package/server/mind/life/presence.js +305 -0
- package/server/mind/meetings.js +1069 -0
- package/server/mind/memory-policy.js +48 -0
- package/server/mind/memory.js +888 -0
- package/server/mind/nature.js +262 -0
- package/server/mind/novelty.js +19 -0
- package/server/mind/observations.js +84 -0
- package/server/mind/own-day.js +324 -0
- package/server/mind/own-voice.js +318 -0
- package/server/mind/periods.js +118 -0
- package/server/mind/reading.js +120 -0
- package/server/mind/salience.js +201 -0
- package/server/mind/schema.js +562 -0
- package/server/mind/self.js +484 -0
- package/server/mind/senses.js +53 -0
- package/server/mind/thoughts.js +274 -0
- package/server/mind/time/activity-clock.js +196 -0
- package/server/mind/time/agenda.js +242 -0
- package/server/mind/time/api.js +200 -0
- package/server/mind/time/attention.js +34 -0
- package/server/mind/time/availability.js +69 -0
- package/server/mind/time/commitment-review.js +198 -0
- package/server/mind/time/delivery-links.js +130 -0
- package/server/mind/time/executor.js +512 -0
- package/server/mind/time/experiences.js +128 -0
- package/server/mind/time/games.js +562 -0
- package/server/mind/time/index.js +739 -0
- package/server/mind/time/intent.js +166 -0
- package/server/mind/time/kinds.js +171 -0
- package/server/mind/time/model-material.js +77 -0
- package/server/mind/time/plugin-activity.js +207 -0
- package/server/mind/time/presentation.js +51 -0
- package/server/mind/time/schedule.js +83 -0
- package/server/mind/time/schema.js +84 -0
- package/server/mind/time/search.js +383 -0
- package/server/mind/time/sharing.js +472 -0
- package/server/mind/time/task-links.js +311 -0
- package/server/mind/time/tasks.js +587 -0
- package/server/mind/time/works.js +432 -0
- package/server/mind/traits.js +191 -0
- package/server/mind/util.js +110 -0
- package/server/mind/view.js +273 -0
- package/server/paths.js +102 -0
- package/server/plugins/api.js +204 -0
- package/server/plugins/host.js +967 -0
- package/server/plugins/install.js +295 -0
- package/server/plugins/manifest.js +201 -0
- package/server/plugins/permissions.js +42 -0
- package/server/plugins/runner/guard.mjs +36 -0
- package/server/plugins/runner/worker.mjs +250 -0
- package/server/plugins/schema.js +91 -0
- package/server/plugins/zip.js +112 -0
- package/server/startup-lock.js +118 -0
- package/server/storage/archive.js +241 -0
- package/server/storage/backup-cleanup-worker.js +8 -0
- package/server/storage/backup-cleanup.js +414 -0
- package/server/storage/backup-lock.js +76 -0
- package/server/storage/backup-scheduler.js +131 -0
- package/server/storage/store.js +199 -0
- package/server/studio/display-names.js +138 -0
- package/server/studio/management.js +102 -0
- package/server/studio/readiness.js +190 -0
- package/server/studio/settings.js +236 -0
- package/server/version.js +10 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import { parseSessionKey } from "../channels/session-key.js";
|
|
2
|
+
import { initiativeContext, initiativeSnapshot } from "./initiative-context.js";
|
|
3
|
+
import { withFallback } from "./model-manager.js";
|
|
4
|
+
import { replyPrompt, prompts } from "./persona-manager.js";
|
|
5
|
+
import { buildContext, perceive } from "./context-builder.js";
|
|
6
|
+
import {
|
|
7
|
+
loadVisionImages,
|
|
8
|
+
visionInputs,
|
|
9
|
+
classifyVision,
|
|
10
|
+
classifyLoaded,
|
|
11
|
+
descriptionsFor,
|
|
12
|
+
mergeVision,
|
|
13
|
+
saveVision,
|
|
14
|
+
markVisionSeen,
|
|
15
|
+
visionWindow,
|
|
16
|
+
} from "./vision-manager.js";
|
|
17
|
+
import { normalizeTurn, takeTurn } from "./turn.js";
|
|
18
|
+
import {
|
|
19
|
+
initiativeAudience,
|
|
20
|
+
originSummary,
|
|
21
|
+
} from "../mind/conversation-origin.js";
|
|
22
|
+
const OCCASIONS = {
|
|
23
|
+
wake: "你刚睡醒,看到了睡着时收到的消息。它们是之前发的,按消息时间理解,可以自然地说刚看到。",
|
|
24
|
+
backlog:
|
|
25
|
+
"这些是你刚才没细看的消息,现在回头看了一眼,已经隔了一会儿,不必每条都回。",
|
|
26
|
+
outreach:
|
|
27
|
+
"这是你自己先前想说的一句(occasion.thought),可能来自你正在为自己而活的事。现在仍然合适就说这一句,不合适就 silent。occasion.returned 是你写下这句话之后又见到的人;人已经出现了,就按现在的情况决定,不要假装还没见过。不要改口说没人找你,不要把留下对方当成对方的责任,不要把「我只是助手」当成收尾。",
|
|
28
|
+
presence:
|
|
29
|
+
"你自己在这里待了一会儿。不是等待一个必须回答的问题;看看自己的兴趣、念头、心情和对人的感觉,此刻想不想说点什么,由你选择。",
|
|
30
|
+
};
|
|
31
|
+
function isPrivateSession(session) {
|
|
32
|
+
if (String(session).startsWith("preview:")) return true;
|
|
33
|
+
try {
|
|
34
|
+
return parseSessionKey(session).kind === "private";
|
|
35
|
+
} catch {
|
|
36
|
+
return String(session).startsWith("private");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function isFormatError(error) {
|
|
40
|
+
return (
|
|
41
|
+
error instanceof SyntaxError ||
|
|
42
|
+
/JSON|Unexpected token|格式|气泡|bubbles|choice|输出被截断/i.test(
|
|
43
|
+
error?.message || "",
|
|
44
|
+
)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Owns this part of the lifecycle; the facade keeps the shared runtime state.
|
|
49
|
+
export class TurnProcessor {
|
|
50
|
+
constructor(owner) {
|
|
51
|
+
this.owner = owner;
|
|
52
|
+
}
|
|
53
|
+
async process(
|
|
54
|
+
session,
|
|
55
|
+
batch,
|
|
56
|
+
{
|
|
57
|
+
replay = false,
|
|
58
|
+
simulated = false,
|
|
59
|
+
preview = null,
|
|
60
|
+
occasion: planned = null,
|
|
61
|
+
backlog = false,
|
|
62
|
+
anchor = null,
|
|
63
|
+
} = {},
|
|
64
|
+
) {
|
|
65
|
+
let occasion = planned;
|
|
66
|
+
const ownInitiative =
|
|
67
|
+
!batch.length && ["presence", "outreach"].includes(planned?.type);
|
|
68
|
+
const simulatedTurn =
|
|
69
|
+
!replay && (simulated || !!preview || batch.some((m) => m.simulated));
|
|
70
|
+
const live = !replay && !simulatedTurn;
|
|
71
|
+
const clock = () =>
|
|
72
|
+
replay || simulatedTurn
|
|
73
|
+
? (batch.at(-1)?.time ?? this.owner.now())
|
|
74
|
+
: this.owner.now();
|
|
75
|
+
const nature = preview?.nature
|
|
76
|
+
? { ...this.owner.mind.nature.current(), ...preview.nature }
|
|
77
|
+
: this.owner.mind.traits.effective(
|
|
78
|
+
this.owner.mind.nature.current(clock()),
|
|
79
|
+
clock(),
|
|
80
|
+
);
|
|
81
|
+
let watermark = batch.at(-1)?.seq ?? this.owner.repo.latest(session);
|
|
82
|
+
const eventMode = simulatedTurn;
|
|
83
|
+
let resolved = null;
|
|
84
|
+
let gate = null;
|
|
85
|
+
if (live && !occasion) {
|
|
86
|
+
if (!this.owner.enabled(session, { simulated: false })) {
|
|
87
|
+
// She is not in this conversation; what passes here is not unread.
|
|
88
|
+
this.owner.mind.look(session, watermark, this.owner.now());
|
|
89
|
+
} else {
|
|
90
|
+
resolved = perceive(
|
|
91
|
+
this.owner.repo,
|
|
92
|
+
session,
|
|
93
|
+
watermark,
|
|
94
|
+
eventMode,
|
|
95
|
+
nature.name,
|
|
96
|
+
);
|
|
97
|
+
gate = this.owner.gate(session, batch, resolved, clock(), nature);
|
|
98
|
+
if (!gate.look && backlog)
|
|
99
|
+
return { status: "glanced", reason: gate.reason };
|
|
100
|
+
if (backlog) occasion = { type: "backlog" };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const trace = this.owner.repo.trace(
|
|
104
|
+
session,
|
|
105
|
+
replay ? "replay" : preview ? "preview" : simulatedTurn ? "demo" : "live",
|
|
106
|
+
);
|
|
107
|
+
const policy = this.owner.policy(preview?.viewSession || session);
|
|
108
|
+
const state = this.owner.sessionState(session);
|
|
109
|
+
const clearEpoch = this.owner.clearEpoch.get(session) || 0;
|
|
110
|
+
const privateChat = isPrivateSession(session);
|
|
111
|
+
if (!replay && !preview)
|
|
112
|
+
for (const m of batch)
|
|
113
|
+
this.owner.repo.db
|
|
114
|
+
.prepare(
|
|
115
|
+
"UPDATE core_jobs SET status='running',trace_id=? WHERE seq=?",
|
|
116
|
+
)
|
|
117
|
+
.run(trace.id, m.seq);
|
|
118
|
+
const finish = (status, reason) => {
|
|
119
|
+
trace.reason = reason;
|
|
120
|
+
if (!replay && !preview)
|
|
121
|
+
for (const m of batch)
|
|
122
|
+
this.owner.repo.db
|
|
123
|
+
.prepare("UPDATE core_jobs SET status=? WHERE seq=?")
|
|
124
|
+
.run(status, m.seq);
|
|
125
|
+
this.owner.repo.finish(trace, status);
|
|
126
|
+
if (
|
|
127
|
+
!replay &&
|
|
128
|
+
!preview &&
|
|
129
|
+
this.owner.repo.db
|
|
130
|
+
.prepare("SELECT id FROM sessions WHERE id=?")
|
|
131
|
+
.get(session)
|
|
132
|
+
)
|
|
133
|
+
this.owner.store.log(
|
|
134
|
+
session,
|
|
135
|
+
"语境决策",
|
|
136
|
+
reason,
|
|
137
|
+
trace.sent?.join("\n") || "",
|
|
138
|
+
Number(!!simulatedTurn),
|
|
139
|
+
);
|
|
140
|
+
return trace;
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
if (
|
|
144
|
+
!replay &&
|
|
145
|
+
!preview &&
|
|
146
|
+
!this.owner.enabled(session, { simulated: simulatedTurn })
|
|
147
|
+
) {
|
|
148
|
+
this.owner.mind.unlived(batch.map((m) => m.seq));
|
|
149
|
+
return finish("silent", "会话已暂停或处于模拟模式");
|
|
150
|
+
}
|
|
151
|
+
if (gate) {
|
|
152
|
+
trace.attention = {
|
|
153
|
+
look: gate.look,
|
|
154
|
+
score: gate.score,
|
|
155
|
+
threshold: gate.threshold,
|
|
156
|
+
reason: gate.reason,
|
|
157
|
+
};
|
|
158
|
+
if (!gate.look) {
|
|
159
|
+
if (gate.defer) this.owner.mind.defer(session);
|
|
160
|
+
// A glance still counts as seeing who is around; a closer look
|
|
161
|
+
// records it after the turn, once she has noticed any absence.
|
|
162
|
+
else
|
|
163
|
+
this.owner.mind.bonds.meet(
|
|
164
|
+
batch
|
|
165
|
+
.filter((m) => m.role === "user")
|
|
166
|
+
.map((m) => ({ userId: String(m.userId), name: m.name })),
|
|
167
|
+
session,
|
|
168
|
+
this.owner.now(),
|
|
169
|
+
);
|
|
170
|
+
return finish(gate.defer ? "deferred" : "glanced", gate.reason);
|
|
171
|
+
}
|
|
172
|
+
batch = gate.rows.length ? gate.rows : batch;
|
|
173
|
+
watermark = Math.max(watermark, batch.at(-1).seq);
|
|
174
|
+
}
|
|
175
|
+
if (!replay && !preview)
|
|
176
|
+
await this.owner.restoreQuotes(session, batch, trace);
|
|
177
|
+
const prompt = prompts(this.owner.repo);
|
|
178
|
+
const now = clock();
|
|
179
|
+
const hasKey =
|
|
180
|
+
this.owner.store.settings().apiKey || process.env.LLM_API_KEY;
|
|
181
|
+
let model;
|
|
182
|
+
try {
|
|
183
|
+
model = this.owner.models.profile();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
// A fresh installation has no model yet; the offline sample keeps the
|
|
186
|
+
// simulator usable. A live turn surfaces the configuration error.
|
|
187
|
+
if (
|
|
188
|
+
preview ||
|
|
189
|
+
(simulatedTurn && this.owner.store.settings().demo && !hasKey)
|
|
190
|
+
)
|
|
191
|
+
return this.owner.offline(session, batch, trace, finish, [], preview);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
const models = withFallback(
|
|
195
|
+
this.owner.models,
|
|
196
|
+
this.owner.fallbackFor(policy, model, trace),
|
|
197
|
+
);
|
|
198
|
+
resolved ||= perceive(
|
|
199
|
+
this.owner.repo,
|
|
200
|
+
session,
|
|
201
|
+
watermark,
|
|
202
|
+
eventMode,
|
|
203
|
+
nature.name,
|
|
204
|
+
);
|
|
205
|
+
const batchIds = batch.map((m) => m.seq);
|
|
206
|
+
const window = resolved.filter((m) => !m.referenceOnly).slice(-60);
|
|
207
|
+
// The speakers in this batch are the people whose history matters now.
|
|
208
|
+
// Pulling everyone from a long room window made unrelated plans compete
|
|
209
|
+
// with the exchange actually taking place.
|
|
210
|
+
const people = [
|
|
211
|
+
...new Set(
|
|
212
|
+
batch.filter((m) => m.role === "user").map((m) => String(m.userId)),
|
|
213
|
+
),
|
|
214
|
+
];
|
|
215
|
+
if (ownInitiative && privateChat && !people.length) {
|
|
216
|
+
try {
|
|
217
|
+
people.push(String(parseSessionKey(session).nativeId));
|
|
218
|
+
} catch {
|
|
219
|
+
/* An unparseable destination has no person to infer. */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Replays exclude mutable memory so later corrections cannot leak into
|
|
223
|
+
// the past; her inner state is folded as of the replayed moment.
|
|
224
|
+
const memories =
|
|
225
|
+
policy.memory && !replay && !ownInitiative
|
|
226
|
+
? this.owner.mind.memory.retrieve(session, batch, now, {
|
|
227
|
+
people,
|
|
228
|
+
touch: live,
|
|
229
|
+
forSpeech: true,
|
|
230
|
+
})
|
|
231
|
+
: [];
|
|
232
|
+
const knowledge = ownInitiative
|
|
233
|
+
? []
|
|
234
|
+
: replay
|
|
235
|
+
? this.owner.knowledge.retrieve(session, batch, now)
|
|
236
|
+
: await this.owner.knowledge.retrieveWithEmbed(session, batch, now);
|
|
237
|
+
const summaryView =
|
|
238
|
+
!ownInitiative && !simulatedTurn && policy.compaction !== false
|
|
239
|
+
? this.owner.compactor.forPrompt(session, {
|
|
240
|
+
before: replay ? batch[0].seq : Number.MAX_SAFE_INTEGER,
|
|
241
|
+
timeZone: policy.timeZone,
|
|
242
|
+
})
|
|
243
|
+
: { summaries: [], coverage: 0, start: 0 };
|
|
244
|
+
const stages =
|
|
245
|
+
policy.memory && !ownInitiative
|
|
246
|
+
? this.owner.repo.db
|
|
247
|
+
.prepare(
|
|
248
|
+
"SELECT first_seq,last_seq,data FROM core_stages WHERE session_id=? AND last_seq<=? ORDER BY last_seq DESC LIMIT 5",
|
|
249
|
+
)
|
|
250
|
+
.all(
|
|
251
|
+
session,
|
|
252
|
+
summaryView.summaries.length
|
|
253
|
+
? summaryView.start - 1
|
|
254
|
+
: watermark,
|
|
255
|
+
)
|
|
256
|
+
.map((s) => ({
|
|
257
|
+
first_seq: s.first_seq,
|
|
258
|
+
last_seq: s.last_seq,
|
|
259
|
+
summary: JSON.parse(s.data).summary,
|
|
260
|
+
reliability:
|
|
261
|
+
"未核实的阶段线索,不是人物事实;冲突时以原文和记忆为准",
|
|
262
|
+
}))
|
|
263
|
+
: [];
|
|
264
|
+
const view = this.owner.mind.view({
|
|
265
|
+
session: preview?.viewSession || session,
|
|
266
|
+
kind: privateChat ? "private" : "group",
|
|
267
|
+
people,
|
|
268
|
+
cue: batch.map((m) => ({
|
|
269
|
+
role: m.role,
|
|
270
|
+
userId: m.userId,
|
|
271
|
+
text: m.text || "",
|
|
272
|
+
relation: resolved.find((line) => line.id === m.seq)?.relation,
|
|
273
|
+
mentioned: m.mentioned === true,
|
|
274
|
+
})),
|
|
275
|
+
now,
|
|
276
|
+
});
|
|
277
|
+
const snapshot = ownInitiative
|
|
278
|
+
? initiativeSnapshot(session, window, nature, now, policy.timeZone)
|
|
279
|
+
: buildContext(
|
|
280
|
+
this.owner.repo,
|
|
281
|
+
session,
|
|
282
|
+
watermark,
|
|
283
|
+
model,
|
|
284
|
+
policy,
|
|
285
|
+
batchIds,
|
|
286
|
+
memories,
|
|
287
|
+
nature,
|
|
288
|
+
now,
|
|
289
|
+
{
|
|
290
|
+
knowledge,
|
|
291
|
+
stages,
|
|
292
|
+
simulated: simulatedTurn,
|
|
293
|
+
summaries: summaryView.summaries,
|
|
294
|
+
coverage: summaryView.coverage,
|
|
295
|
+
summaryStart: summaryView.start,
|
|
296
|
+
self: view.self,
|
|
297
|
+
inner: view.inner,
|
|
298
|
+
nameOf: (id) => this.owner.mind.bonds.name(id),
|
|
299
|
+
resolved,
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
if (ownInitiative) {
|
|
303
|
+
snapshot.self = view.self;
|
|
304
|
+
snapshot.inner = view.inner;
|
|
305
|
+
}
|
|
306
|
+
const visionModel = model.vision
|
|
307
|
+
? model
|
|
308
|
+
: this.owner.fallbackFor(null, model).find((item) => item.vision) ||
|
|
309
|
+
model;
|
|
310
|
+
const direct = snapshot.batch.some((m) => m.relation === "direct");
|
|
311
|
+
const media = ownInitiative
|
|
312
|
+
? { images: [], unavailable: [] }
|
|
313
|
+
: visionInputs(snapshot, visionModel, {
|
|
314
|
+
selective: !!policy.selectiveVision,
|
|
315
|
+
});
|
|
316
|
+
const early = classifyVision(
|
|
317
|
+
this.owner.repo.db,
|
|
318
|
+
snapshot.sessionId,
|
|
319
|
+
media.images,
|
|
320
|
+
);
|
|
321
|
+
const loaded = early.pending.length
|
|
322
|
+
? await this.owner.loadVisionImages(early.pending, {
|
|
323
|
+
sessionId: snapshot.sessionId,
|
|
324
|
+
fetchImage: this.owner.fetchImage,
|
|
325
|
+
})
|
|
326
|
+
: { images: [], unavailable: [] };
|
|
327
|
+
const planned = classifyLoaded(
|
|
328
|
+
this.owner.repo.db,
|
|
329
|
+
snapshot.sessionId,
|
|
330
|
+
loaded.images,
|
|
331
|
+
{
|
|
332
|
+
batchIds: snapshot.batchIds,
|
|
333
|
+
direct,
|
|
334
|
+
modelCanSee: !!model.vision,
|
|
335
|
+
},
|
|
336
|
+
);
|
|
337
|
+
mergeVision(snapshot, [...early.cached, ...planned.cached]);
|
|
338
|
+
if (early.cached.length || planned.cached.length)
|
|
339
|
+
trace.steps.push("图片使用已保存的观察,不再提交画面");
|
|
340
|
+
const describe = planned.describe;
|
|
341
|
+
let show = planned.show.slice();
|
|
342
|
+
if (!replay)
|
|
343
|
+
snapshot.topics = this.owner.topics.recent(session, watermark, now);
|
|
344
|
+
snapshot.unavailableImages = [
|
|
345
|
+
...media.unavailable,
|
|
346
|
+
...loaded.unavailable,
|
|
347
|
+
];
|
|
348
|
+
trace.snapshot = { ...snapshot, sourceRows: undefined, batch: undefined };
|
|
349
|
+
trace.config = {
|
|
350
|
+
nature: { ...nature },
|
|
351
|
+
policy,
|
|
352
|
+
model: { ...model, apiKey: undefined },
|
|
353
|
+
prompts: prompt,
|
|
354
|
+
};
|
|
355
|
+
trace.affect = view.affect;
|
|
356
|
+
if (
|
|
357
|
+
simulatedTurn &&
|
|
358
|
+
!hasKey &&
|
|
359
|
+
(preview || this.owner.store.settings().demo)
|
|
360
|
+
)
|
|
361
|
+
return this.owner.offline(
|
|
362
|
+
session,
|
|
363
|
+
batch,
|
|
364
|
+
trace,
|
|
365
|
+
finish,
|
|
366
|
+
snapshot.messages,
|
|
367
|
+
preview,
|
|
368
|
+
state,
|
|
369
|
+
);
|
|
370
|
+
if (describe.length) {
|
|
371
|
+
try {
|
|
372
|
+
const observed = await models.call(
|
|
373
|
+
visionModel,
|
|
374
|
+
"vision",
|
|
375
|
+
prompt.system +
|
|
376
|
+
"\n" +
|
|
377
|
+
prompt.vision +
|
|
378
|
+
"\n每张图按 messageId 独立观察。description 只写可见元素、文字和表情语气;前后语境仅用来消歧,不把当前话题、发送意图或给谁看的猜测写进可缓存的描述。表情包台词不是事实,不从前一张图推断这一张。",
|
|
379
|
+
{
|
|
380
|
+
messages: visionWindow(snapshot.messages, [
|
|
381
|
+
...describe.map((image) => image.messageId),
|
|
382
|
+
...snapshot.batchIds,
|
|
383
|
+
]),
|
|
384
|
+
batchIds: snapshot.batchIds,
|
|
385
|
+
},
|
|
386
|
+
trace,
|
|
387
|
+
describe,
|
|
388
|
+
);
|
|
389
|
+
const paired = descriptionsFor(describe, observed);
|
|
390
|
+
if (paired.length) {
|
|
391
|
+
saveVision(this.owner.repo.db, snapshot.sessionId, paired);
|
|
392
|
+
mergeVision(
|
|
393
|
+
snapshot,
|
|
394
|
+
paired.map(({ image, description }) => ({
|
|
395
|
+
messageId: image.messageId,
|
|
396
|
+
description,
|
|
397
|
+
})),
|
|
398
|
+
);
|
|
399
|
+
} else if (
|
|
400
|
+
observed &&
|
|
401
|
+
typeof observed === "object" &&
|
|
402
|
+
!snapshot.vision
|
|
403
|
+
)
|
|
404
|
+
snapshot.vision = observed;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
trace.steps.push(
|
|
407
|
+
`图片理解失败,本轮不根据画面编造:${error.message}`,
|
|
408
|
+
);
|
|
409
|
+
snapshot.unavailableImages.push(
|
|
410
|
+
...describe.map((image) => ({
|
|
411
|
+
messageId: image.messageId,
|
|
412
|
+
reason: "图片理解调用失败",
|
|
413
|
+
})),
|
|
414
|
+
);
|
|
415
|
+
if (model.vision) show = show.concat(describe);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (snapshot.vision) trace.snapshot.vision = snapshot.vision;
|
|
419
|
+
if (!ownInitiative) {
|
|
420
|
+
await this.owner.perceivers.apply(snapshot, { replay, now });
|
|
421
|
+
const offered = this.owner.capabilities.offered({
|
|
422
|
+
kind: privateChat ? "private" : "group",
|
|
423
|
+
now,
|
|
424
|
+
});
|
|
425
|
+
if (offered.length) snapshot.canDo = offered;
|
|
426
|
+
}
|
|
427
|
+
const generationImages = model.vision ? show : [];
|
|
428
|
+
if (!replay && !preview) {
|
|
429
|
+
const rounds = this.owner.repo.db
|
|
430
|
+
.prepare(
|
|
431
|
+
"SELECT COUNT(DISTINCT trace_id) n FROM core_outbox WHERE session_id=? AND time>? AND status IN ('confirmed','uncertain','sending')",
|
|
432
|
+
)
|
|
433
|
+
.get(session, this.owner.now() - 60000).n;
|
|
434
|
+
const roundLimit = privateChat ? 30 : direct ? 20 : 6;
|
|
435
|
+
if (rounds >= roundLimit)
|
|
436
|
+
return finish("silent", `每分钟发言轮数限速(${roundLimit}轮)`);
|
|
437
|
+
}
|
|
438
|
+
const pressure = this.owner.mind.budget.pressure("conversation", now);
|
|
439
|
+
const occasionData = occasion
|
|
440
|
+
? {
|
|
441
|
+
type: occasion.type,
|
|
442
|
+
note: OCCASIONS[occasion.type] || "",
|
|
443
|
+
...occasion.data,
|
|
444
|
+
}
|
|
445
|
+
: undefined;
|
|
446
|
+
if (["presence", "outreach"].includes(occasion?.type)) {
|
|
447
|
+
const expression = occasionData.expression;
|
|
448
|
+
const audience = initiativeAudience(
|
|
449
|
+
this.owner.mind,
|
|
450
|
+
{ sources: expression?.sources || [], created: expression?.formedAt },
|
|
451
|
+
session,
|
|
452
|
+
now,
|
|
453
|
+
);
|
|
454
|
+
if (!audience.allowed) return finish("silent", audience.reason);
|
|
455
|
+
if (expression)
|
|
456
|
+
expression.audience = {
|
|
457
|
+
...audience,
|
|
458
|
+
origin: originSummary(audience.origin),
|
|
459
|
+
};
|
|
460
|
+
snapshot.initiative = occasionData;
|
|
461
|
+
trace.snapshot = initiativeContext(snapshot);
|
|
462
|
+
}
|
|
463
|
+
if (
|
|
464
|
+
live &&
|
|
465
|
+
!occasion &&
|
|
466
|
+
this.owner.batchMovedOn(session, batch, snapshot, privateChat)
|
|
467
|
+
) {
|
|
468
|
+
trace.steps.push(
|
|
469
|
+
"读房间的这几秒里,要回的人又说了话:先不花一次调用,并入下一批一起回",
|
|
470
|
+
);
|
|
471
|
+
return finish("stale", "读房间时又有新话,并入下一批");
|
|
472
|
+
}
|
|
473
|
+
let turn;
|
|
474
|
+
try {
|
|
475
|
+
turn = normalizeTurn(
|
|
476
|
+
await takeTurn(
|
|
477
|
+
models,
|
|
478
|
+
model,
|
|
479
|
+
replyPrompt(
|
|
480
|
+
nature,
|
|
481
|
+
prompt,
|
|
482
|
+
snapshot.initiative ? "initiative" : "turn",
|
|
483
|
+
),
|
|
484
|
+
snapshot,
|
|
485
|
+
trace,
|
|
486
|
+
{
|
|
487
|
+
images: generationImages,
|
|
488
|
+
occasion: occasionData,
|
|
489
|
+
pressure:
|
|
490
|
+
pressure >= 0.7 ? "今天已经说了很多话,能短就短" : undefined,
|
|
491
|
+
},
|
|
492
|
+
),
|
|
493
|
+
snapshot,
|
|
494
|
+
trace,
|
|
495
|
+
);
|
|
496
|
+
if (generationImages.length)
|
|
497
|
+
markVisionSeen(
|
|
498
|
+
this.owner.repo.db,
|
|
499
|
+
snapshot.sessionId,
|
|
500
|
+
generationImages,
|
|
501
|
+
);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (!isFormatError(error)) throw error;
|
|
504
|
+
trace.steps.push("回合输出格式异常,按直接对话兜底");
|
|
505
|
+
if (["presence", "outreach"].includes(occasion?.type))
|
|
506
|
+
return finish("error", "主动判断没有整理好,愿望保留,稍后重新决定");
|
|
507
|
+
turn = normalizeTurn(
|
|
508
|
+
{
|
|
509
|
+
choice:
|
|
510
|
+
direct && !["presence", "outreach"].includes(occasion?.type)
|
|
511
|
+
? "speak"
|
|
512
|
+
: "silent",
|
|
513
|
+
reason: "没能整理好想法",
|
|
514
|
+
},
|
|
515
|
+
snapshot,
|
|
516
|
+
trace,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
trace.decision = turn;
|
|
520
|
+
if (live) this.owner.mind.time.adjust(turn, clock());
|
|
521
|
+
trace.path = occasion?.type || (direct ? "direct" : "contextual");
|
|
522
|
+
const roomKind = privateChat ? "private" : "group";
|
|
523
|
+
let talkSettled = false;
|
|
524
|
+
const land = (spoke) => {
|
|
525
|
+
if (!live || talkSettled) return;
|
|
526
|
+
talkSettled = true;
|
|
527
|
+
const choice =
|
|
528
|
+
spoke || turn.choice === "silent" ? turn.choice : "silent";
|
|
529
|
+
this.owner.mind.settleTalk(
|
|
530
|
+
{ ...turn, choice },
|
|
531
|
+
{
|
|
532
|
+
session,
|
|
533
|
+
snapshot,
|
|
534
|
+
kind: roomKind,
|
|
535
|
+
spoke: choice !== "silent",
|
|
536
|
+
withheld: !spoke && turn.choice !== "silent",
|
|
537
|
+
sent: trace.sent || [],
|
|
538
|
+
time: now,
|
|
539
|
+
},
|
|
540
|
+
);
|
|
541
|
+
this.owner.mind.choose({
|
|
542
|
+
session,
|
|
543
|
+
traceId: trace.id,
|
|
544
|
+
choice,
|
|
545
|
+
appraisal: turn.appraisal,
|
|
546
|
+
reason: choice === turn.choice ? turn.reason : "没发出去",
|
|
547
|
+
watermark,
|
|
548
|
+
occasion: occasion?.type || null,
|
|
549
|
+
time: now,
|
|
550
|
+
});
|
|
551
|
+
};
|
|
552
|
+
if (live) {
|
|
553
|
+
this.owner.mind.look(session, watermark, now);
|
|
554
|
+
this.owner.mind.experience(turn, {
|
|
555
|
+
session,
|
|
556
|
+
snapshot,
|
|
557
|
+
kind: roomKind,
|
|
558
|
+
spoke: false,
|
|
559
|
+
deferTalk: true,
|
|
560
|
+
time: now,
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
if (!replay && !preview)
|
|
564
|
+
this.owner.topics.record(session, trace.id, watermark, turn);
|
|
565
|
+
if (live && !ownInitiative && turn.act) {
|
|
566
|
+
snapshot.actionResult = await this.owner.capabilities.accept(turn.act, {
|
|
567
|
+
session,
|
|
568
|
+
kind: roomKind,
|
|
569
|
+
traceId: trace.id,
|
|
570
|
+
});
|
|
571
|
+
trace.action = snapshot.actionResult;
|
|
572
|
+
if (["done", "failed"].includes(snapshot.actionResult?.state))
|
|
573
|
+
turn.bubbles = [];
|
|
574
|
+
}
|
|
575
|
+
if (turn.choice === "silent") {
|
|
576
|
+
land(false);
|
|
577
|
+
return finish("silent", turn.reason);
|
|
578
|
+
}
|
|
579
|
+
let spoken;
|
|
580
|
+
try {
|
|
581
|
+
spoken = await this.owner.speak({
|
|
582
|
+
session,
|
|
583
|
+
batch,
|
|
584
|
+
turn,
|
|
585
|
+
snapshot,
|
|
586
|
+
trace,
|
|
587
|
+
finish,
|
|
588
|
+
models,
|
|
589
|
+
model,
|
|
590
|
+
nature,
|
|
591
|
+
prompt,
|
|
592
|
+
policy,
|
|
593
|
+
state,
|
|
594
|
+
watermark,
|
|
595
|
+
clearEpoch,
|
|
596
|
+
privateChat,
|
|
597
|
+
direct,
|
|
598
|
+
simulatedTurn,
|
|
599
|
+
replay,
|
|
600
|
+
preview,
|
|
601
|
+
pressure,
|
|
602
|
+
generationImages,
|
|
603
|
+
anchor,
|
|
604
|
+
});
|
|
605
|
+
} catch (error) {
|
|
606
|
+
land(Array.isArray(trace.sent) && trace.sent.length > 0);
|
|
607
|
+
trace.error = error.message;
|
|
608
|
+
if (live && !trace.sent?.length && (privateChat || gate?.direct))
|
|
609
|
+
this.owner.retryAfterOutage(session, batch, error, trace);
|
|
610
|
+
return finish("error", error.message);
|
|
611
|
+
}
|
|
612
|
+
land(Array.isArray(spoken?.sent) && spoken.sent.length > 0);
|
|
613
|
+
return spoken;
|
|
614
|
+
} catch (e) {
|
|
615
|
+
trace.error = e.message;
|
|
616
|
+
if (live && !trace.sent?.length && (privateChat || gate?.direct))
|
|
617
|
+
this.owner.retryAfterOutage(session, batch, e, trace);
|
|
618
|
+
return finish("error", e.message);
|
|
619
|
+
} finally {
|
|
620
|
+
// Previews and demos are disposable by design: they never advance
|
|
621
|
+
// memory cursors or create stages.
|
|
622
|
+
if (live)
|
|
623
|
+
try {
|
|
624
|
+
this.owner.backgroundWork(session, policy);
|
|
625
|
+
} catch {
|
|
626
|
+
// Background upkeep must never turn a finished turn into an error.
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|