luckytri 1.0.8 → 1.1.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 +1 -1
- package/README.md +1 -1
- package/docs/en/CHANGELOG.md +44 -24
- package/docs/en/architecture.md +4 -0
- package/docs/en/cognition.md +34 -0
- package/docs/en/guide.md +8 -6
- package/docs/zh/CHANGELOG.md +44 -24
- package/docs/zh/architecture.md +4 -0
- package/docs/zh/cognition.md +34 -0
- package/docs/zh/guide.md +8 -6
- package/package.json +112 -112
- package/public/app/assets/ChatsPage-DH4ZPRXB.js +3 -0
- package/public/app/assets/{Empty-C7yzOBzy.js → Empty-DRsHeX-L.js} +1 -1
- package/public/app/assets/Field-BfIUcaNa.js +1 -0
- package/public/app/assets/HeartPage-BxgSUJJ_.js +1 -0
- package/public/app/assets/LifePage-CAKr8h_q.js +1 -0
- package/public/app/assets/MemoryPage-CFq8L_TC.js +1 -0
- package/public/app/assets/NaturePage-BU1iDno3.js +3 -0
- package/public/app/assets/PeoplePage-C6i_nAfr.js +1 -0
- package/public/app/assets/PluginsPage-DRV8lzk3.js +2 -0
- package/public/app/assets/SystemPage-21AlQBiC.js +1 -0
- package/public/app/assets/SystemPage-CMcuUdLm.css +1 -0
- package/public/app/assets/TimePage-DUzjLnZV.js +3 -0
- package/public/app/assets/index-WAvrlAcO.js +4 -0
- package/public/app/assets/{knowledge-BJOIyuJo.js → knowledge-V_blbx9R.js} +1 -1
- package/public/app/assets/media-C7j5HqxK.js +1 -0
- package/public/app/assets/{mind-DtTVrW6Z.js → mind-BBAstMqH.js} +1 -1
- package/public/app/index.html +3 -3
- 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/backoff.js +123 -0
- package/server/core/context-builder.js +307 -288
- package/server/core/contribution-check.js +50 -0
- package/server/core/conversation-cues.js +377 -385
- package/server/core/conversation-grounding.js +547 -304
- package/server/core/dialogue-context.js +247 -244
- package/server/core/dialogue-contract.js +33 -9
- package/server/core/embedding.js +246 -0
- package/server/core/expression-strategy.js +90 -0
- package/server/core/initiative-context.js +88 -68
- package/server/core/model-json.js +223 -190
- package/server/core/model-manager.js +256 -56
- package/server/core/model-presets.js +1045 -900
- package/server/core/orchestrator.js +41 -3
- package/server/core/persona-manager.js +364 -359
- package/server/core/reader-check.js +166 -62
- package/server/core/reply-delivery.js +688 -537
- package/server/core/repository.js +36 -0
- package/server/core/response-generator.js +101 -92
- package/server/core/response-validator.js +18 -3
- package/server/core/retired-prompts.js +53 -51
- package/server/core/time-expressions.js +143 -0
- package/server/core/turn-processor.js +175 -5
- package/server/core/turn.js +299 -44
- package/server/core/understanding.js +85 -0
- package/server/core/working-memory.js +149 -0
- package/server/i18n/decisions.js +225 -209
- package/server/i18n/errors.js +14 -0
- package/server/i18n/models.js +120 -111
- package/server/knowledge/api.js +362 -342
- package/server/knowledge/embedding-profile.js +158 -114
- package/server/knowledge/manager.js +6 -1
- package/server/mind/api.js +68 -0
- package/server/mind/continuity.js +634 -562
- package/server/mind/day-planner.js +52 -3
- package/server/mind/episodes.js +242 -0
- package/server/mind/expression-grounding.js +24 -21
- package/server/mind/index.js +13 -2
- package/server/mind/initiative.js +356 -338
- package/server/mind/life/index.js +6 -0
- package/server/mind/meetings.js +1112 -1069
- package/server/mind/memory.js +1091 -888
- package/server/mind/recall/index.js +776 -0
- package/server/mind/recall/schema.js +132 -0
- package/server/mind/recall/sources.js +220 -0
- package/server/mind/recall/store.js +368 -0
- package/server/mind/salience.js +202 -201
- package/server/mind/schema.js +568 -567
- package/server/mind/self.js +537 -496
- package/server/mind/time/activity-recall.js +65 -0
- package/server/mind/time/index.js +6 -2
- package/server/mind/time/tasks.js +36 -10
- package/server/mind/time/works.js +528 -527
- package/server/mind/view.js +63 -17
- package/server/storage/archive.js +1 -1
- package/public/app/assets/ChatsPage-CshdtTzV.js +0 -3
- package/public/app/assets/Field-BjFv4Rfn.js +0 -1
- package/public/app/assets/HeartPage-eqo6ru16.js +0 -1
- package/public/app/assets/LifePage-CN8GE5NK.js +0 -1
- package/public/app/assets/MemoryPage-CCpMN4Wg.js +0 -1
- package/public/app/assets/NaturePage-C0JqdBTR.js +0 -3
- package/public/app/assets/PeoplePage-BXGBbO4u.js +0 -1
- package/public/app/assets/PluginsPage-Dksp5AV5.js +0 -2
- package/public/app/assets/SystemPage-BenIyTvk.css +0 -1
- package/public/app/assets/SystemPage-Cd7aa8eE.js +0 -1
- package/public/app/assets/TimePage-Bs_zImb4.js +0 -3
- package/public/app/assets/index-CyRtYxP3.js +0 -4
- package/public/app/assets/media-Bwtx8blz.js +0 -1
|
@@ -9,7 +9,18 @@ import {
|
|
|
9
9
|
} from "./time/availability.js";
|
|
10
10
|
import { rhythmPhase } from "./nature.js";
|
|
11
11
|
import { isExecutable, pluginAvailable, withActivities } from "./time/kinds.js";
|
|
12
|
+
import { providerWaitMs } from "../core/backoff.js";
|
|
12
13
|
const MINUTE = 60000;
|
|
14
|
+
// A task that came back without a plan waits longer each time. Asking again
|
|
15
|
+
// every ten minutes spent hundreds of calls a day on the same answer.
|
|
16
|
+
export const DAY_PLAN_SCHEDULE = [
|
|
17
|
+
10 * MINUTE,
|
|
18
|
+
30 * MINUTE,
|
|
19
|
+
2 * 60 * MINUTE,
|
|
20
|
+
6 * 60 * MINUTE,
|
|
21
|
+
12 * 60 * MINUTE,
|
|
22
|
+
];
|
|
23
|
+
const planKey = (id) => `day-plan:${id}`;
|
|
13
24
|
|
|
14
25
|
export function chosenSlot(value, life, now, requestedAt = now) {
|
|
15
26
|
const at = value.startAt
|
|
@@ -126,7 +137,9 @@ export class DayPlanner {
|
|
|
126
137
|
(t) =>
|
|
127
138
|
(!primary || t.priority > primary.priority) &&
|
|
128
139
|
this.needs(t, now) &&
|
|
129
|
-
(state.requested ||
|
|
140
|
+
(state.requested ||
|
|
141
|
+
(now - (attempts[t.id] || 0) >= 10 * MINUTE &&
|
|
142
|
+
life.mind.backoff.ready(planKey(t.id), now))),
|
|
130
143
|
)
|
|
131
144
|
.sort((a, b) => time.tasks.score(b, now) - time.tasks.score(a, now));
|
|
132
145
|
const task = tasks.find((t) => !unsupportedAction(t)) || tasks[0];
|
|
@@ -155,7 +168,8 @@ export class DayPlanner {
|
|
|
155
168
|
trace = life.repo.trace("__mind__", "activity"),
|
|
156
169
|
runId = life.run("day-plan", "想具体做什么、几点开始和做多久");
|
|
157
170
|
let status = "error",
|
|
158
|
-
reason = ""
|
|
171
|
+
reason = "",
|
|
172
|
+
failure = null;
|
|
159
173
|
try {
|
|
160
174
|
const projects = time.works
|
|
161
175
|
.projects({ limit: 20 })
|
|
@@ -275,11 +289,34 @@ export class DayPlanner {
|
|
|
275
289
|
const project = projects.find(
|
|
276
290
|
(p) => p.id === answer.projectId && p.kind === activity,
|
|
277
291
|
);
|
|
292
|
+
// The chosen time may already be taken. Move to the next free
|
|
293
|
+
// stretch she is awake for, instead of asking the model again.
|
|
294
|
+
const wanted = Math.max(slot.startAt, task.ready_at);
|
|
295
|
+
const startAt = time.tasks.freeStart(
|
|
296
|
+
task.id,
|
|
297
|
+
wanted,
|
|
298
|
+
slot.durationMinutes,
|
|
299
|
+
life.now(),
|
|
300
|
+
{
|
|
301
|
+
acceptable: (at) =>
|
|
302
|
+
rhythmPhase(
|
|
303
|
+
life.mind.nature.current(at),
|
|
304
|
+
at,
|
|
305
|
+
life.mind.timeZone(),
|
|
306
|
+
).key !== "asleep",
|
|
307
|
+
},
|
|
308
|
+
);
|
|
309
|
+
if (startAt === null) throw Error("一周内没有空出的时间段");
|
|
310
|
+
if (startAt !== wanted)
|
|
311
|
+
slot.reason = text(
|
|
312
|
+
`${slot.reason}(原定时间已有安排,顺延到 ${localClock(startAt, life.mind.timeZone()).local})`,
|
|
313
|
+
240,
|
|
314
|
+
);
|
|
278
315
|
time.tasks.arrange(
|
|
279
316
|
task.id,
|
|
280
317
|
{
|
|
281
318
|
...slot,
|
|
282
|
-
startAt
|
|
319
|
+
startAt,
|
|
283
320
|
activity,
|
|
284
321
|
title:
|
|
285
322
|
task.kind === "promise" && task.activity !== "unknown"
|
|
@@ -296,8 +333,20 @@ export class DayPlanner {
|
|
|
296
333
|
} catch (error) {
|
|
297
334
|
reason = text(error.message, 160);
|
|
298
335
|
trace.error = reason;
|
|
336
|
+
failure = error;
|
|
299
337
|
return { status: "error", reason };
|
|
300
338
|
} finally {
|
|
339
|
+
if (status === "planned") life.mind.backoff.succeed(planKey(task.id));
|
|
340
|
+
else if (["waiting", "resting", "error"].includes(status))
|
|
341
|
+
life.mind.backoff.fail(
|
|
342
|
+
planKey(task.id),
|
|
343
|
+
failure || reason || status,
|
|
344
|
+
life.now(),
|
|
345
|
+
{
|
|
346
|
+
schedule: DAY_PLAN_SCHEDULE,
|
|
347
|
+
minimumMs: providerWaitMs(failure),
|
|
348
|
+
},
|
|
349
|
+
);
|
|
301
350
|
this.working = false;
|
|
302
351
|
life.chat.finishQuietly(
|
|
303
352
|
trace,
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { applySpeakerNames } from "../core/speaker-names.js";
|
|
3
|
+
import { elapsedLabel } from "./clock.js";
|
|
4
|
+
import { secretRequest } from "./guard.js";
|
|
5
|
+
import { isPrivateSession } from "./memory.js";
|
|
6
|
+
import { grounded } from "./salience.js";
|
|
7
|
+
import { hasCredential, parse, text, zonedTime } from "./util.js";
|
|
8
|
+
|
|
9
|
+
export const EPISODE_STATUS = new Set(["open", "resolved", "abandoned"]);
|
|
10
|
+
const RELATIONS = new Set(["continues", "resolves"]);
|
|
11
|
+
|
|
12
|
+
// An affair from its start to how it stands now: an interview that was
|
|
13
|
+
// coming, then happened, then had a result. Each step is its own row in the
|
|
14
|
+
// same thread, citing the messages it rests on, so the past is never
|
|
15
|
+
// rewritten and recall can tell what happened, what changed and what is
|
|
16
|
+
// still open.
|
|
17
|
+
export class Episodes {
|
|
18
|
+
constructor(mind) {
|
|
19
|
+
this.mind = mind;
|
|
20
|
+
this.db = mind.db;
|
|
21
|
+
}
|
|
22
|
+
get(id) {
|
|
23
|
+
return this.db.prepare("SELECT * FROM mind_episodes WHERE id=?").get(id);
|
|
24
|
+
}
|
|
25
|
+
thread(threadId, before = Number.MAX_SAFE_INTEGER) {
|
|
26
|
+
return this.db
|
|
27
|
+
.prepare(
|
|
28
|
+
"SELECT * FROM mind_episodes WHERE thread_id=? AND created<=? ORDER BY created,rowid",
|
|
29
|
+
)
|
|
30
|
+
.all(threadId, before);
|
|
31
|
+
}
|
|
32
|
+
latest(threadId, before = Number.MAX_SAFE_INTEGER) {
|
|
33
|
+
return this.thread(threadId, before).at(-1) || null;
|
|
34
|
+
}
|
|
35
|
+
visible(row, session) {
|
|
36
|
+
if (row.discretion === "open") return true;
|
|
37
|
+
return this.mind.memory.scopes(session).includes(row.session_id);
|
|
38
|
+
}
|
|
39
|
+
// Threads still open that concern these people or this room, newest
|
|
40
|
+
// first, as seen at `now`.
|
|
41
|
+
open({ session, people = [], now = Date.now(), limit = 6 } = {}) {
|
|
42
|
+
const rows = this.db
|
|
43
|
+
.prepare(
|
|
44
|
+
`SELECT e.* FROM mind_episodes e JOIN (
|
|
45
|
+
SELECT thread_id,MAX(created) last FROM mind_episodes WHERE created<=? GROUP BY thread_id
|
|
46
|
+
) l ON l.thread_id=e.thread_id AND l.last=e.created
|
|
47
|
+
WHERE e.status='open' ORDER BY e.created DESC LIMIT 200`,
|
|
48
|
+
)
|
|
49
|
+
.all(now);
|
|
50
|
+
const who = new Set(people.map(String));
|
|
51
|
+
return rows
|
|
52
|
+
.filter((row) => this.visible(row, session))
|
|
53
|
+
.filter(
|
|
54
|
+
(row) =>
|
|
55
|
+
row.session_id === session ||
|
|
56
|
+
parse(row.participants, []).some((id) => who.has(String(id))),
|
|
57
|
+
)
|
|
58
|
+
.slice(0, limit);
|
|
59
|
+
}
|
|
60
|
+
due(now = Date.now(), limit = 4) {
|
|
61
|
+
return this.db
|
|
62
|
+
.prepare(
|
|
63
|
+
`SELECT e.* FROM mind_episodes e JOIN (
|
|
64
|
+
SELECT thread_id,MAX(created) last FROM mind_episodes WHERE created<=? GROUP BY thread_id
|
|
65
|
+
) l ON l.thread_id=e.thread_id AND l.last=e.created
|
|
66
|
+
WHERE e.status='open' AND e.follow_up_at>0 AND e.follow_up_at<=?
|
|
67
|
+
ORDER BY e.follow_up_at LIMIT ?`,
|
|
68
|
+
)
|
|
69
|
+
.all(now, now, limit);
|
|
70
|
+
}
|
|
71
|
+
// Open threads offered to the consolidation model so a new step can
|
|
72
|
+
// continue or resolve one instead of starting a duplicate.
|
|
73
|
+
candidates(session, block, now = Date.now()) {
|
|
74
|
+
const people = [
|
|
75
|
+
...new Set(
|
|
76
|
+
block.filter((m) => m.role === "user").map((m) => String(m.userId)),
|
|
77
|
+
),
|
|
78
|
+
];
|
|
79
|
+
const refs = new Map();
|
|
80
|
+
const list = this.open({ session, people, now, limit: 8 }).map((row, i) => {
|
|
81
|
+
const ref = `e${i + 1}`;
|
|
82
|
+
refs.set(ref, row);
|
|
83
|
+
return {
|
|
84
|
+
ref,
|
|
85
|
+
title: row.title,
|
|
86
|
+
sofar: text(row.outcome || row.content, 120),
|
|
87
|
+
since: elapsedLabel(
|
|
88
|
+
row.happened_start || row.created,
|
|
89
|
+
now,
|
|
90
|
+
this.mind.timeZone(),
|
|
91
|
+
),
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
return { list, refs };
|
|
95
|
+
}
|
|
96
|
+
// Writes what the consolidation model reported, keeping only steps whose
|
|
97
|
+
// words were actually said in the cited messages of this stretch.
|
|
98
|
+
record(
|
|
99
|
+
list,
|
|
100
|
+
{ block, session, refs = new Map(), names, privateChat, now, runId = null },
|
|
101
|
+
) {
|
|
102
|
+
const written = [];
|
|
103
|
+
const bySeq = new Map(block.map((m) => [m.seq, m]));
|
|
104
|
+
for (const raw of (Array.isArray(list) ? list : []).slice(0, 6)) {
|
|
105
|
+
if (
|
|
106
|
+
!raw ||
|
|
107
|
+
typeof raw.title !== "string" ||
|
|
108
|
+
typeof raw.content !== "string"
|
|
109
|
+
)
|
|
110
|
+
continue;
|
|
111
|
+
const cited = (Array.isArray(raw.sources) ? raw.sources : [])
|
|
112
|
+
.map(Number)
|
|
113
|
+
.map((seq) => bySeq.get(seq))
|
|
114
|
+
.filter(Boolean);
|
|
115
|
+
if (!cited.length || cited.some((m) => m.artifact)) continue;
|
|
116
|
+
const words = cited.map((m) => m.text || "");
|
|
117
|
+
const content = text(raw.content, 300);
|
|
118
|
+
const outcome = text(raw.outcome, 160);
|
|
119
|
+
if (
|
|
120
|
+
hasCredential(`${raw.title}${content}${outcome}`) ||
|
|
121
|
+
!grounded(content, words) ||
|
|
122
|
+
(outcome && !grounded(outcome, words))
|
|
123
|
+
)
|
|
124
|
+
continue;
|
|
125
|
+
const status = EPISODE_STATUS.has(raw.status) ? raw.status : "open";
|
|
126
|
+
const relation = RELATIONS.has(raw.relation)
|
|
127
|
+
? raw.relation
|
|
128
|
+
: status === "resolved"
|
|
129
|
+
? "resolves"
|
|
130
|
+
: "continues";
|
|
131
|
+
const previous = raw.continues ? refs.get(String(raw.continues)) : null;
|
|
132
|
+
const speakers = new Set(
|
|
133
|
+
cited.filter((m) => m.role === "user").map((m) => String(m.userId)),
|
|
134
|
+
);
|
|
135
|
+
const participants = [
|
|
136
|
+
...new Set([
|
|
137
|
+
...speakers,
|
|
138
|
+
...(Array.isArray(raw.participants) ? raw.participants : [])
|
|
139
|
+
.map(String)
|
|
140
|
+
.filter((id) => block.some((m) => String(m.userId) === id)),
|
|
141
|
+
]),
|
|
142
|
+
];
|
|
143
|
+
const quiet = block.some(
|
|
144
|
+
(m) =>
|
|
145
|
+
m.role === "user" &&
|
|
146
|
+
m.seq <= Math.max(...cited.map((c) => c.seq)) &&
|
|
147
|
+
secretRequest(m.text || ""),
|
|
148
|
+
);
|
|
149
|
+
const discretion =
|
|
150
|
+
quiet || previous?.discretion === "secret"
|
|
151
|
+
? "secret"
|
|
152
|
+
: privateChat || previous?.discretion === "private"
|
|
153
|
+
? "private"
|
|
154
|
+
: "open";
|
|
155
|
+
const follow = raw.followUp
|
|
156
|
+
? zonedTime(String(raw.followUp), this.mind.timeZone())
|
|
157
|
+
: null;
|
|
158
|
+
const id = randomUUID();
|
|
159
|
+
const thread = previous?.thread_id || id;
|
|
160
|
+
this.db
|
|
161
|
+
.prepare(
|
|
162
|
+
`INSERT INTO mind_episodes(id,thread_id,created,session_id,title,content,outcome,status,participants,
|
|
163
|
+
happened_start,happened_end,follow_up_at,basis,importance,discretion,sources,run_id)
|
|
164
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
165
|
+
)
|
|
166
|
+
.run(
|
|
167
|
+
id,
|
|
168
|
+
thread,
|
|
169
|
+
now,
|
|
170
|
+
previous?.session_id && previous.discretion !== "open"
|
|
171
|
+
? previous.session_id
|
|
172
|
+
: session,
|
|
173
|
+
text(applySpeakerNames(raw.title, names), 40),
|
|
174
|
+
applySpeakerNames(content, names),
|
|
175
|
+
applySpeakerNames(outcome, names),
|
|
176
|
+
status,
|
|
177
|
+
JSON.stringify(participants),
|
|
178
|
+
previous?.happened_start || Math.min(...cited.map((m) => m.time)),
|
|
179
|
+
status === "open" ? null : Math.max(...cited.map((m) => m.time)),
|
|
180
|
+
status === "open" && Number.isFinite(follow) && follow > now
|
|
181
|
+
? follow
|
|
182
|
+
: null,
|
|
183
|
+
raw.basis === "told" ? "stated" : "witnessed",
|
|
184
|
+
Math.min(1, Math.max(0, Number(raw.importance) || 0.5)),
|
|
185
|
+
discretion,
|
|
186
|
+
JSON.stringify(cited.map((m) => m.seq)),
|
|
187
|
+
runId,
|
|
188
|
+
);
|
|
189
|
+
if (previous)
|
|
190
|
+
this.db
|
|
191
|
+
.prepare(
|
|
192
|
+
"INSERT OR IGNORE INTO mind_links(from_id,to_id,relation,created,sources) VALUES (?,?,?,?,?)",
|
|
193
|
+
)
|
|
194
|
+
.run(
|
|
195
|
+
`episode:${id}`,
|
|
196
|
+
`episode:${previous.id}`,
|
|
197
|
+
relation,
|
|
198
|
+
now,
|
|
199
|
+
JSON.stringify(cited.map((m) => m.seq)),
|
|
200
|
+
);
|
|
201
|
+
this.mind.recall?.sync("episode", id);
|
|
202
|
+
written.push(id);
|
|
203
|
+
}
|
|
204
|
+
return written;
|
|
205
|
+
}
|
|
206
|
+
// One remembered affair, told as it went and as it stands: the beginning,
|
|
207
|
+
// what changed and where it is now. A private thread stays private.
|
|
208
|
+
recollection(row, session, now = Date.now()) {
|
|
209
|
+
const steps = this.thread(row.thread_id, now);
|
|
210
|
+
const last = steps.at(-1) || row;
|
|
211
|
+
const first = steps[0] || row;
|
|
212
|
+
const zone = this.mind.timeZone();
|
|
213
|
+
const local = this.mind.memory.scopes(session).includes(last.session_id);
|
|
214
|
+
return {
|
|
215
|
+
kind: "episode",
|
|
216
|
+
title: last.title,
|
|
217
|
+
began: elapsedLabel(first.happened_start || first.created, now, zone),
|
|
218
|
+
...(steps.length > 1
|
|
219
|
+
? {
|
|
220
|
+
sofar: steps
|
|
221
|
+
.slice(0, -1)
|
|
222
|
+
.slice(-2)
|
|
223
|
+
.map((s) => text(s.outcome || s.content, 60)),
|
|
224
|
+
}
|
|
225
|
+
: {}),
|
|
226
|
+
now: text(last.outcome || last.content, 120),
|
|
227
|
+
status:
|
|
228
|
+
last.status === "open"
|
|
229
|
+
? last.follow_up_at && last.follow_up_at <= now
|
|
230
|
+
? "还没结果,说好的时间已经过了"
|
|
231
|
+
: "还没结束"
|
|
232
|
+
: last.status === "resolved"
|
|
233
|
+
? "已经有结果"
|
|
234
|
+
: "后来放下了",
|
|
235
|
+
updated: elapsedLabel(last.created, now, zone),
|
|
236
|
+
...(last.basis === "stated" ? { basis: "对方告诉你的" } : {}),
|
|
237
|
+
...(!local && isPrivateSession(last.session_id)
|
|
238
|
+
? { discretion: "私下知道的,不要当众说出口,也不要说出从哪听来" }
|
|
239
|
+
: {}),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
export function claimsLivedAction(content) {
|
|
2
|
-
return String(content || "")
|
|
3
|
-
.split(/[。!?\n]/)
|
|
4
|
-
.some(
|
|
5
|
-
(clause) =>
|
|
6
|
-
!/想|打算|准备|希望|假如|设想|如果/.test(clause) &&
|
|
7
|
-
/(?:刚才|刚刚|今天|这次|然后|真的|已经)[^。!?]{0,25}(
|
|
8
|
-
clause,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
1
|
+
export function claimsLivedAction(content) {
|
|
2
|
+
return String(content || "")
|
|
3
|
+
.split(/[。!?\n]/)
|
|
4
|
+
.some(
|
|
5
|
+
(clause) =>
|
|
6
|
+
!/想|打算|准备|希望|假如|设想|如果/.test(clause) &&
|
|
7
|
+
(/(?:刚才|刚刚|今天|这次|然后|真的|已经)[^。!?]{0,25}(?:读了|读上|读完|读[一二两三几\d]+[段页章节遍]|坐下来读|翻开|翻出来|写完|完成了|打开了|开始读|动手了)/.test(
|
|
8
|
+
clause,
|
|
9
|
+
) ||
|
|
10
|
+
/我(?:自己)?[^。!?]{0,10}(?:读上了|开始读了|翻开了|完成了|写完了)/.test(
|
|
11
|
+
clause,
|
|
12
|
+
)),
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function claimsSharedHistory(content) {
|
|
17
|
+
return /(?:之前|上次|早些时候)[^。!?\n]{0,25}(?:他|她|对方|那个人|你)[^。!?\n]{0,20}(?:说过|聊过|看过|读过|回话|私下)|(?:他|她|对方|那个人|你)[^。!?\n]{0,15}(?:之前|上次|读过|看过|说过)/.test(
|
|
18
|
+
String(content || ""),
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const EXPRESSION_GROUNDING = `核对一个准备保存的自我念头是否把愿望写成行动,或把别人的话题误认成共同经历。只输出JSON。
|
|
23
|
+
candidate只是草稿,不是事实。notes/self是过去的想法、愿望或理解;它们可以支持新的感受和看法,不能单独证明这次真的开始、读完或做完。actions/currentLife是实际活动记录。sources只表示引用了哪里,不自动证明动作。按实际记录核对这次声称新做过的具体动作和对象;不能用无关作品或一条待办证明完成。声称共同经历时,还须核对实际参与者和各自说过的话;在别处读到另一人的讨论不证明当前对象参与过。自己的新想法、偏好、假设、打算可以没有执行记录,也不需要已采取外部行动。只指出确实无依据的新经历与归属,不评文风。
|
|
24
|
+
输出 {"ok":true或false,"reason":"具体哪项新经历有或没有实际记录"}。`;
|
package/server/mind/index.js
CHANGED
|
@@ -22,6 +22,10 @@ import { innerView } from "./view.js";
|
|
|
22
22
|
import { Senses } from "./senses.js";
|
|
23
23
|
import { Observations } from "./observations.js";
|
|
24
24
|
import { clamp, dayKey, parse, text } from "./util.js";
|
|
25
|
+
import { Backoff } from "../core/backoff.js";
|
|
26
|
+
import { EmbeddingService } from "../core/embedding.js";
|
|
27
|
+
import { Recall } from "./recall/index.js";
|
|
28
|
+
import { Episodes } from "./episodes.js";
|
|
25
29
|
|
|
26
30
|
// The wish a day's snapshot says she was living. Older snapshots did not
|
|
27
31
|
// store it; those fall back to her own-life threads from that day.
|
|
@@ -86,11 +90,13 @@ const TURN_CHANGES = new Set(
|
|
|
86
90
|
// One of her, across every conversation. Sessions only say where something
|
|
87
91
|
// happened; they are no longer walls between parts of her.
|
|
88
92
|
export class Mind {
|
|
89
|
-
constructor(repo, { models } = {}) {
|
|
93
|
+
constructor(repo, { models, now = () => Date.now() } = {}) {
|
|
90
94
|
this.repo = repo;
|
|
91
95
|
this.db = repo.db;
|
|
92
96
|
this.store = repo.store;
|
|
93
97
|
migrateMind(this.db, this.store);
|
|
98
|
+
this.backoff = new Backoff(this.db);
|
|
99
|
+
this.embedding = new EmbeddingService(repo, models, { now });
|
|
94
100
|
this.db.exec(
|
|
95
101
|
"CREATE INDEX IF NOT EXISTS core_events_time ON core_events(time)",
|
|
96
102
|
);
|
|
@@ -113,6 +119,8 @@ export class Mind {
|
|
|
113
119
|
this.time = new TimeSystem(this);
|
|
114
120
|
this.senses = new Senses();
|
|
115
121
|
this.observations = new Observations(this);
|
|
122
|
+
this.episodes = new Episodes(this);
|
|
123
|
+
this.recall = new Recall(this);
|
|
116
124
|
this.body = () => [];
|
|
117
125
|
this.days.reconcile();
|
|
118
126
|
}
|
|
@@ -120,7 +128,7 @@ export class Mind {
|
|
|
120
128
|
return this.repo.config("life", {})?.timeZone || "Asia/Shanghai";
|
|
121
129
|
}
|
|
122
130
|
view(options) {
|
|
123
|
-
return innerView(this, options);
|
|
131
|
+
return this.meetings.memo(() => innerView(this, options));
|
|
124
132
|
}
|
|
125
133
|
// The whole system is one body she can read: what is connected, and
|
|
126
134
|
// whether each plugin is on. The studio shows the same state.
|
|
@@ -361,6 +369,9 @@ export class Mind {
|
|
|
361
369
|
} else if (kind === "observation") {
|
|
362
370
|
tomb(this.observations.revoke(id));
|
|
363
371
|
} else throw Error("不能撤销这类内容");
|
|
372
|
+
// A withdrawn memory is no longer searchable either.
|
|
373
|
+
if (["self", "meeting", "thought", "anticipation"].includes(kind))
|
|
374
|
+
this.recall.sync(kind, id);
|
|
364
375
|
this.store.revision++;
|
|
365
376
|
}
|
|
366
377
|
revocations(limit = 100) {
|