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.
Files changed (95) hide show
  1. package/README.en.md +14 -12
  2. package/README.md +4 -2
  3. package/docs/en/CHANGELOG.md +24 -0
  4. package/docs/en/architecture.md +12 -10
  5. package/docs/en/chat-style.md +13 -13
  6. package/docs/en/cognition.md +12 -4
  7. package/docs/en/evolution.md +35 -0
  8. package/docs/en/her-life.md +5 -2
  9. package/docs/en/vision.md +50 -0
  10. package/docs/zh/CHANGELOG.md +24 -0
  11. package/docs/zh/architecture.md +12 -10
  12. package/docs/zh/chat-style.md +17 -17
  13. package/docs/zh/cognition.md +12 -4
  14. package/docs/zh/evolution.md +35 -0
  15. package/docs/zh/her-life.md +5 -2
  16. package/docs/zh/vision.md +50 -0
  17. package/package.json +4 -1
  18. package/public/app/assets/{ChatsPage-IfBgNdaD.js → ChatsPage-eDqFIg97.js} +1 -1
  19. package/public/app/assets/{Empty-DK9nQPCf.js → Empty-CAGOJsfD.js} +1 -1
  20. package/public/app/assets/{Field-BzKFhuFd.js → Field-CRz-JmN1.js} +1 -1
  21. package/public/app/assets/{HeartPage-CKxrbfFQ.js → HeartPage-BGWoeMrC.js} +1 -1
  22. package/public/app/assets/{LifePage-CtxPEi6E.js → LifePage-iao3o3wt.js} +1 -1
  23. package/public/app/assets/{MemoryPage-B8FLXzLi.js → MemoryPage-Dwi2yi7E.js} +1 -1
  24. package/public/app/assets/NaturePage-DniImo9U.js +3 -0
  25. package/public/app/assets/{NaturePage-CHkpb5NK.css → NaturePage-zguDpGWY.css} +1 -1
  26. package/public/app/assets/{PeoplePage-D3yn0HV0.js → PeoplePage-D-MC3JEt.js} +1 -1
  27. package/public/app/assets/{PluginsPage-ojv8zawj.js → PluginsPage-D7dJTf96.js} +1 -1
  28. package/public/app/assets/SystemPage-C5CUHjGh.js +1 -0
  29. package/public/app/assets/{SystemPage-CGVTvNZw.css → SystemPage-C_Y5M7JU.css} +1 -1
  30. package/public/app/assets/{TimePage-Dyd-FFbq.js → TimePage-Dwhr3-92.js} +1 -1
  31. package/public/app/assets/{index-BJfzfgnm.js → index-L0bHax8C.js} +3 -3
  32. package/public/app/assets/{knowledge-C-bND0Ub.js → knowledge-BTDNafzl.js} +1 -1
  33. package/public/app/assets/{media-D7G3Nco6.js → media-BBzOAfNG.js} +1 -1
  34. package/public/app/assets/{mind-ec-G6iNZ.js → mind-CBTkFje2.js} +1 -1
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +3 -3
  37. package/public/guide.html +3 -3
  38. package/scripts/migrate-instance.js +27 -0
  39. package/scripts/update-instance.js +21 -2
  40. package/server/channels/contract.js +5 -1
  41. package/server/channels/hub.js +2 -2
  42. package/server/channels/onebot/adapter.js +291 -288
  43. package/server/channels/onebot/index.js +2 -2
  44. package/server/channels/qqbot/api.js +194 -199
  45. package/server/core/context-builder.js +20 -3
  46. package/server/core/conversation-cues.js +5 -4
  47. package/server/core/conversation-grounding.js +20 -17
  48. package/server/core/conversation-needs.js +32 -0
  49. package/server/core/dialogue-context.js +2 -5
  50. package/server/core/dialogue-contract.js +23 -16
  51. package/server/core/message-scheduler.js +85 -78
  52. package/server/core/model-manager.js +113 -16
  53. package/server/core/model-presets.js +1 -1
  54. package/server/core/orchestrator.js +18 -2
  55. package/server/core/persona-manager.js +14 -5
  56. package/server/core/reader-check.js +47 -42
  57. package/server/core/reply-delivery.js +125 -34
  58. package/server/core/response-validator.js +28 -1
  59. package/server/core/retired-prompts.js +5 -0
  60. package/server/core/speech-length.js +43 -0
  61. package/server/core/turn-processor.js +38 -5
  62. package/server/core/turn.js +70 -16
  63. package/server/i18n/errors.js +30 -0
  64. package/server/i18n/labels.js +5 -0
  65. package/server/index.js +3 -0
  66. package/server/knowledge/embedding-profile.js +126 -29
  67. package/server/knowledge/manager.js +103 -34
  68. package/server/knowledge/schema.js +197 -158
  69. package/server/mind/affect.js +267 -265
  70. package/server/mind/aftermath.js +169 -0
  71. package/server/mind/anticipations.js +2 -0
  72. package/server/mind/clock.js +91 -83
  73. package/server/mind/discretion.js +20 -0
  74. package/server/mind/episodes.js +2 -1
  75. package/server/mind/index.js +17 -2
  76. package/server/mind/initiative.js +29 -14
  77. package/server/mind/memory.js +47 -5
  78. package/server/mind/recall/cue.js +67 -0
  79. package/server/mind/recall/index.js +183 -29
  80. package/server/mind/recall/roots.js +231 -0
  81. package/server/mind/recall/schema.js +2 -0
  82. package/server/mind/recall/store.js +113 -30
  83. package/server/mind/salience.js +22 -1
  84. package/server/mind/schema.js +5 -0
  85. package/server/mind/thoughts.js +276 -274
  86. package/server/mind/view.js +100 -60
  87. package/server/plugins/install.js +1 -6
  88. package/server/plugins/zip.js +18 -4
  89. package/server/runtime/data-migration.js +193 -0
  90. package/server/runtime/update-execution.js +43 -4
  91. package/server/runtime/update-manager.js +4 -1
  92. package/server/storage/archive.js +1 -1
  93. package/server/storage/backup-scheduler.js +15 -3
  94. package/public/app/assets/NaturePage-C7i607ul.js +0 -3
  95. package/public/app/assets/SystemPage-Cv3rFvyT.js +0 -1
@@ -67,9 +67,14 @@ function allowedCollection(db, collection, session) {
67
67
  }
68
68
 
69
69
  export class KnowledgeManager {
70
- constructor(repo, models) {
70
+ // `embedding` is her one embedding service. When given, a question is
71
+ // embedded through it: the vector the recall index just asked for is
72
+ // reused from its cache, a slow service gives up after a few seconds
73
+ // instead of holding the reply, and repeated failures pause it for both.
74
+ constructor(repo, models, { embedding = null } = {}) {
71
75
  this.repo = repo;
72
76
  this.models = models;
77
+ this.embedding = embedding;
73
78
  }
74
79
  collections(session = "") {
75
80
  return this.repo.db
@@ -269,8 +274,11 @@ export class KnowledgeManager {
269
274
  (!signature ||
270
275
  chunk.embedding_profile === signature ||
271
276
  (legacy && !chunk.embedding_profile))
272
- )
273
- score += cosine(vec, unpackVector(chunk.embedding)) * 4;
277
+ ) {
278
+ // A vector of another length (an old unsigned chunk) is not comparable.
279
+ const stored = unpackVector(chunk.embedding);
280
+ if (stored?.length === vec.length) score += cosine(vec, stored) * 4;
281
+ }
274
282
  const ageDays = (cutoff - (chunk.document_created || 0)) / 86400000;
275
283
  if (ageDays > 30) score *= 0.85;
276
284
  if (score > (best.get(chunk.id) || 0)) best.set(chunk.id, score);
@@ -308,11 +316,20 @@ export class KnowledgeManager {
308
316
  .get();
309
317
  if (profile.embedding && queries.length && searchable) {
310
318
  try {
311
- vectors = await embedBatches(
312
- this.models,
313
- profile,
314
- queries.map((query) => query.slice(0, 2000)),
315
- );
319
+ vectors = this.embedding
320
+ ? this.embedding.available()
321
+ ? (
322
+ await this.embedding.embed(queries, {
323
+ purpose: "query",
324
+ session,
325
+ })
326
+ ).map((vector) => Array.from(vector))
327
+ : []
328
+ : await embedBatches(
329
+ this.models,
330
+ profile,
331
+ queries.map((query) => query.slice(0, 2000)),
332
+ );
316
333
  } catch {
317
334
  vectors = [];
318
335
  }
@@ -322,7 +339,7 @@ export class KnowledgeManager {
322
339
  async reembedDocument(id) {
323
340
  const rows = this.repo.db
324
341
  .prepare(
325
- "SELECT id,text FROM core_chunks WHERE document_id=? ORDER BY ordinal",
342
+ "SELECT id,text,embedding_profile,previous_profile FROM core_chunks WHERE document_id=? ORDER BY ordinal",
326
343
  )
327
344
  .all(id);
328
345
  if (!rows.length) throw Error("文档没有可建立向量的段落");
@@ -331,37 +348,89 @@ export class KnowledgeManager {
331
348
  this.models,
332
349
  );
333
350
  if (!profile.embedding) throw Error("请先启用知识向量模型");
334
- const vectors = await embedBatches(
351
+ const { vectors, ask } = await this.#vectorsFor(rows, signature, (texts) =>
352
+ embedBatches(this.models, profile, texts),
353
+ );
354
+ if (!this.#saveVectors(rows, vectors, signature, id))
355
+ throw Error("文档已变化,请重新建立向量");
356
+ return { chunks: rows.length, embedded: ask.length };
357
+ }
358
+ // After a model or length change, chunks made under the old profile are
359
+ // searched by words only. This brings them over a few at a time on the
360
+ // maintenance tick, through the paced embedding service. Returns how many
361
+ // chunks moved; 0 when nothing is left or the service is not available.
362
+ async reembedStale({ limit = 20 } = {}) {
363
+ if (!this.embedding?.available()) return 0;
364
+ const { signature, legacy } = resolveEmbeddingProfile(
365
+ this.repo,
335
366
  this.models,
336
- profile,
337
- rows.map((row) => row.text),
338
367
  );
368
+ if (!signature) return 0;
369
+ const rows = this.repo.db
370
+ .prepare(
371
+ `SELECT c.id,c.text,c.embedding_profile,c.previous_profile FROM core_chunks c
372
+ JOIN core_documents d ON d.id=c.document_id
373
+ WHERE d.status='ready' AND c.embedding IS NOT NULL AND c.embedding_profile!=?
374
+ ${legacy ? "AND c.embedding_profile!=''" : ""}
375
+ ORDER BY c.created DESC LIMIT ?`,
376
+ )
377
+ .all(signature, limit);
378
+ if (!rows.length) return 0;
379
+ const { vectors } = await this.#vectorsFor(rows, signature, (texts) =>
380
+ this.embedding.embed(texts, { purpose: "document" }),
381
+ );
382
+ this.#saveVectors(rows, vectors, signature);
383
+ return rows.length;
384
+ }
385
+ // A chunk whose previous vector already belongs to this profile swaps
386
+ // back; only the rest are asked for.
387
+ async #vectorsFor(rows, signature, embed) {
388
+ const ask = rows.filter((row) => row.previous_profile !== signature);
389
+ const made = ask.length ? await embed(ask.map((row) => row.text)) : [];
390
+ const vectors = new Map(ask.map((row, i) => [row.id, made[i]]));
391
+ return { vectors, ask };
392
+ }
393
+ // The current vector becomes the previous one, so nothing is thrown away
394
+ // until a third profile arrives. A trigger re-indexes the chunk.
395
+ #saveVectors(rows, vectors, signature, documentId = null) {
339
396
  const db = this.repo.db;
340
- db.exec("SAVEPOINT reembed_document");
397
+ const fresh = db.prepare(
398
+ `UPDATE core_chunks SET
399
+ previous_embedding=CASE WHEN embedding_profile!=? THEN embedding ELSE previous_embedding END,
400
+ previous_profile=CASE WHEN embedding_profile!=? THEN embedding_profile ELSE previous_profile END,
401
+ embedding=?,embedding_profile=?
402
+ WHERE id=? AND text=?`,
403
+ );
404
+ const swap = db.prepare(
405
+ `UPDATE core_chunks SET
406
+ embedding=previous_embedding,embedding_profile=previous_profile,
407
+ previous_embedding=embedding,previous_profile=embedding_profile
408
+ WHERE id=? AND previous_profile=?`,
409
+ );
410
+ db.exec("SAVEPOINT reembed_chunks");
341
411
  try {
342
- const update = db.prepare(
343
- "UPDATE core_chunks SET embedding=?,embedding_profile=? WHERE id=? AND document_id=? AND text=?",
344
- );
345
- rows.forEach((row, index) => {
346
- if (
347
- !update.run(
348
- packVector(vectors[index]),
349
- signature,
350
- row.id,
351
- id,
352
- row.text,
353
- ).changes
354
- )
355
- throw Error("文档已变化,请重新建立向量");
356
- });
357
- db.exec("RELEASE reembed_document");
358
- } catch (error) {
359
- db.exec("ROLLBACK TO reembed_document");
360
- db.exec("RELEASE reembed_document");
361
- throw error;
412
+ for (const row of rows) {
413
+ const vector = vectors.get(row.id);
414
+ const changed = vector
415
+ ? fresh.run(
416
+ signature,
417
+ signature,
418
+ packVector(vector),
419
+ signature,
420
+ row.id,
421
+ row.text,
422
+ ).changes
423
+ : swap.run(row.id, signature).changes;
424
+ if (!changed && documentId) throw Error("changed");
425
+ }
426
+ db.exec("RELEASE reembed_chunks");
427
+ } catch {
428
+ db.exec("ROLLBACK TO reembed_chunks");
429
+ db.exec("RELEASE reembed_chunks");
430
+ return false;
362
431
  }
363
432
  this.repo.store.revision++;
364
- return { chunks: rows.length };
433
+ return true;
365
434
  }
366
435
  removeDocument(id) {
367
436
  const row = this.repo.db
@@ -1,158 +1,197 @@
1
- import { ftsTokens } from "./retrieval.js";
2
-
3
- export function indexMemory(db, id, content) {
4
- db.prepare("DELETE FROM core_memory_fts WHERE memory_id=?").run(id);
5
- const tokens = ftsTokens(content);
6
- if (tokens)
7
- db.prepare(
8
- "INSERT INTO core_memory_fts(memory_id, tokens) VALUES (?,?)",
9
- ).run(id, tokens);
10
- }
11
-
12
- export function indexChunk(db, id, content) {
13
- db.prepare("DELETE FROM core_chunk_fts WHERE chunk_id=?").run(id);
14
- const tokens = ftsTokens(content);
15
- if (tokens)
16
- db.prepare("INSERT INTO core_chunk_fts(chunk_id, tokens) VALUES (?,?)").run(
17
- id,
18
- tokens,
19
- );
20
- }
21
-
22
- function mergeLegacyMemories(db) {
23
- if (db.prepare("SELECT id FROM core_config WHERE id='memory-merge-v1'").get())
24
- return;
25
- db.exec("BEGIN IMMEDIATE");
26
- try {
27
- for (const m of db.prepare("SELECT * FROM memories").all()) {
28
- const sessionId =
29
- m.scope === "shared"
30
- ? "__shared__"
31
- : m.scope === "private"
32
- ? `__private__:${m.user_id}`
33
- : m.scope;
34
- const id = "legacy:" + m.id;
35
- if (db.prepare("SELECT id FROM core_memories WHERE id=?").get(id))
36
- continue;
37
- db.prepare(
38
- "INSERT INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,locked,sources,created,updated) VALUES (?,?,?,?,'reviewed',1,1,'confirmed',1,'[]',?,?)",
39
- ).run(
40
- id,
41
- sessionId,
42
- m.user_id,
43
- m.content,
44
- m.time || Date.now(),
45
- Date.now(),
46
- );
47
- indexMemory(db, id, m.content);
48
- }
49
- db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
50
- "memory-merge-v1",
51
- JSON.stringify({ time: Date.now() }),
52
- );
53
- db.exec("COMMIT");
54
- } catch (e) {
55
- db.exec("ROLLBACK");
56
- throw e;
57
- }
58
- }
59
-
60
- export const KNOWLEDGE_TRIGGERS = [
61
- `CREATE TRIGGER IF NOT EXISTS core_vector_changed AFTER UPDATE OF embedding ON core_chunks BEGIN
62
- DELETE FROM core_vector_buckets WHERE chunk_id=NEW.id;
63
- UPDATE core_chunks SET vector_indexed=0 WHERE id=NEW.id;
64
- END`,
65
- `CREATE TRIGGER IF NOT EXISTS core_vector_deleted AFTER DELETE ON core_chunks BEGIN
66
- DELETE FROM core_vector_buckets WHERE chunk_id=OLD.id;
67
- END`,
68
- ];
69
-
70
- export function migrateKnowledge(db) {
71
- db.exec(`
72
- CREATE TABLE IF NOT EXISTS core_collections (
73
- id TEXT PRIMARY KEY,
74
- name TEXT NOT NULL,
75
- scope TEXT NOT NULL,
76
- created INTEGER NOT NULL,
77
- updated INTEGER NOT NULL
78
- );
79
- CREATE TABLE IF NOT EXISTS core_collection_sessions (
80
- collection_id TEXT NOT NULL,
81
- session_id TEXT NOT NULL,
82
- PRIMARY KEY(collection_id, session_id)
83
- );
84
- CREATE TABLE IF NOT EXISTS core_documents (
85
- id TEXT PRIMARY KEY,
86
- collection_id TEXT NOT NULL,
87
- title TEXT NOT NULL,
88
- source TEXT,
89
- path TEXT,
90
- status TEXT NOT NULL,
91
- error TEXT,
92
- created INTEGER NOT NULL,
93
- updated INTEGER NOT NULL,
94
- bytes INTEGER,
95
- hash TEXT
96
- );
97
- CREATE TABLE IF NOT EXISTS core_chunks (
98
- id TEXT PRIMARY KEY,
99
- document_id TEXT NOT NULL,
100
- collection_id TEXT NOT NULL,
101
- ordinal INTEGER NOT NULL,
102
- heading TEXT,
103
- text TEXT NOT NULL,
104
- tokens TEXT,
105
- embedding BLOB,
106
- hash TEXT,
107
- created INTEGER NOT NULL
108
- );
109
- `);
110
- db.exec(
111
- "CREATE VIRTUAL TABLE IF NOT EXISTS core_memory_fts USING fts5(memory_id UNINDEXED, tokens)",
112
- );
113
- db.exec(
114
- "CREATE VIRTUAL TABLE IF NOT EXISTS core_chunk_fts USING fts5(chunk_id UNINDEXED, tokens)",
115
- );
116
- if (
117
- !db
118
- .prepare("PRAGMA table_info(core_chunks)")
119
- .all()
120
- .some((c) => c.name === "vector_indexed")
121
- )
122
- db.exec(
123
- "ALTER TABLE core_chunks ADD COLUMN vector_indexed INTEGER NOT NULL DEFAULT 0",
124
- );
125
- if (
126
- !db
127
- .prepare("PRAGMA table_info(core_chunks)")
128
- .all()
129
- .some((c) => c.name === "embedding_profile")
130
- )
131
- db.exec(
132
- "ALTER TABLE core_chunks ADD COLUMN embedding_profile TEXT NOT NULL DEFAULT ''",
133
- );
134
- db.exec(`CREATE INDEX IF NOT EXISTS core_chunks_collection ON core_chunks(collection_id, document_id);
135
- CREATE INDEX IF NOT EXISTS core_chunks_unindexed ON core_chunks(vector_indexed) WHERE embedding IS NOT NULL;
136
- CREATE TABLE IF NOT EXISTS core_vector_buckets(dimension INTEGER NOT NULL,band INTEGER NOT NULL,bucket INTEGER NOT NULL,chunk_id TEXT NOT NULL,PRIMARY KEY(dimension,band,bucket,chunk_id));
137
- CREATE INDEX IF NOT EXISTS core_vector_buckets_chunk ON core_vector_buckets(chunk_id);`);
138
- db.exec(KNOWLEDGE_TRIGGERS.join(";") + ";");
139
- if (
140
- !db
141
- .prepare("SELECT id FROM core_collections WHERE id='shared-default'")
142
- .get()
143
- )
144
- db.prepare(
145
- "INSERT INTO core_collections(id,name,scope,created,updated) VALUES ('shared-default','共享知识库','shared',?,?)",
146
- ).run(Date.now(), Date.now());
147
- mergeLegacyMemories(db);
148
- if (
149
- !db.prepare("SELECT id FROM core_config WHERE id='memory-fts-v1'").get()
150
- ) {
151
- for (const m of db.prepare("SELECT id,content FROM core_memories").all())
152
- indexMemory(db, m.id, m.content);
153
- db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
154
- "memory-fts-v1",
155
- JSON.stringify({ time: Date.now() }),
156
- );
157
- }
158
- }
1
+ import { ftsTokens } from "./retrieval.js";
2
+
3
+ export function indexMemory(db, id, content) {
4
+ db.prepare("DELETE FROM core_memory_fts WHERE memory_id=?").run(id);
5
+ const tokens = ftsTokens(content);
6
+ if (tokens)
7
+ db.prepare(
8
+ "INSERT INTO core_memory_fts(memory_id, tokens) VALUES (?,?)",
9
+ ).run(id, tokens);
10
+ }
11
+
12
+ export function indexChunk(db, id, content) {
13
+ db.prepare("DELETE FROM core_chunk_fts WHERE chunk_id=?").run(id);
14
+ const tokens = ftsTokens(content);
15
+ if (tokens)
16
+ db.prepare("INSERT INTO core_chunk_fts(chunk_id, tokens) VALUES (?,?)").run(
17
+ id,
18
+ tokens,
19
+ );
20
+ }
21
+
22
+ function mergeLegacyMemories(db) {
23
+ if (db.prepare("SELECT id FROM core_config WHERE id='memory-merge-v1'").get())
24
+ return;
25
+ db.exec("BEGIN IMMEDIATE");
26
+ try {
27
+ for (const m of db.prepare("SELECT * FROM memories").all()) {
28
+ const sessionId =
29
+ m.scope === "shared"
30
+ ? "__shared__"
31
+ : m.scope === "private"
32
+ ? `__private__:${m.user_id}`
33
+ : m.scope;
34
+ const id = "legacy:" + m.id;
35
+ if (db.prepare("SELECT id FROM core_memories WHERE id=?").get(id))
36
+ continue;
37
+ db.prepare(
38
+ "INSERT INTO core_memories(id,session_id,subject,content,type,confidence,importance,status,locked,sources,created,updated) VALUES (?,?,?,?,'reviewed',1,1,'confirmed',1,'[]',?,?)",
39
+ ).run(
40
+ id,
41
+ sessionId,
42
+ m.user_id,
43
+ m.content,
44
+ m.time || Date.now(),
45
+ Date.now(),
46
+ );
47
+ indexMemory(db, id, m.content);
48
+ }
49
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
50
+ "memory-merge-v1",
51
+ JSON.stringify({ time: Date.now() }),
52
+ );
53
+ db.exec("COMMIT");
54
+ } catch (e) {
55
+ db.exec("ROLLBACK");
56
+ throw e;
57
+ }
58
+ }
59
+
60
+ export const KNOWLEDGE_TRIGGERS = [
61
+ `CREATE TRIGGER IF NOT EXISTS core_vector_changed AFTER UPDATE OF embedding ON core_chunks BEGIN
62
+ DELETE FROM core_vector_buckets WHERE chunk_id=NEW.id;
63
+ UPDATE core_chunks SET vector_indexed=0 WHERE id=NEW.id;
64
+ END`,
65
+ `CREATE TRIGGER IF NOT EXISTS core_vector_deleted AFTER DELETE ON core_chunks BEGIN
66
+ DELETE FROM core_vector_buckets WHERE chunk_id=OLD.id;
67
+ END`,
68
+ ];
69
+
70
+ export function migrateKnowledge(db) {
71
+ db.exec(`
72
+ CREATE TABLE IF NOT EXISTS core_collections (
73
+ id TEXT PRIMARY KEY,
74
+ name TEXT NOT NULL,
75
+ scope TEXT NOT NULL,
76
+ created INTEGER NOT NULL,
77
+ updated INTEGER NOT NULL
78
+ );
79
+ CREATE TABLE IF NOT EXISTS core_collection_sessions (
80
+ collection_id TEXT NOT NULL,
81
+ session_id TEXT NOT NULL,
82
+ PRIMARY KEY(collection_id, session_id)
83
+ );
84
+ CREATE TABLE IF NOT EXISTS core_documents (
85
+ id TEXT PRIMARY KEY,
86
+ collection_id TEXT NOT NULL,
87
+ title TEXT NOT NULL,
88
+ source TEXT,
89
+ path TEXT,
90
+ status TEXT NOT NULL,
91
+ error TEXT,
92
+ created INTEGER NOT NULL,
93
+ updated INTEGER NOT NULL,
94
+ bytes INTEGER,
95
+ hash TEXT
96
+ );
97
+ CREATE TABLE IF NOT EXISTS core_chunks (
98
+ id TEXT PRIMARY KEY,
99
+ document_id TEXT NOT NULL,
100
+ collection_id TEXT NOT NULL,
101
+ ordinal INTEGER NOT NULL,
102
+ heading TEXT,
103
+ text TEXT NOT NULL,
104
+ tokens TEXT,
105
+ embedding BLOB,
106
+ hash TEXT,
107
+ created INTEGER NOT NULL
108
+ );
109
+ `);
110
+ db.exec(
111
+ "CREATE VIRTUAL TABLE IF NOT EXISTS core_memory_fts USING fts5(memory_id UNINDEXED, tokens)",
112
+ );
113
+ db.exec(
114
+ "CREATE VIRTUAL TABLE IF NOT EXISTS core_chunk_fts USING fts5(chunk_id UNINDEXED, tokens)",
115
+ );
116
+ if (
117
+ !db
118
+ .prepare("PRAGMA table_info(core_chunks)")
119
+ .all()
120
+ .some((c) => c.name === "vector_indexed")
121
+ )
122
+ db.exec(
123
+ "ALTER TABLE core_chunks ADD COLUMN vector_indexed INTEGER NOT NULL DEFAULT 0",
124
+ );
125
+ if (
126
+ !db
127
+ .prepare("PRAGMA table_info(core_chunks)")
128
+ .all()
129
+ .some((c) => c.name === "embedding_profile")
130
+ )
131
+ db.exec(
132
+ "ALTER TABLE core_chunks ADD COLUMN embedding_profile TEXT NOT NULL DEFAULT ''",
133
+ );
134
+ // The vector a chunk had under the profile before the current one, so
135
+ // switching back costs no requests.
136
+ for (const [name, type] of [
137
+ ["previous_embedding", "BLOB"],
138
+ ["previous_profile", "TEXT NOT NULL DEFAULT ''"],
139
+ ])
140
+ if (
141
+ !db
142
+ .prepare("PRAGMA table_info(core_chunks)")
143
+ .all()
144
+ .some((c) => c.name === name)
145
+ )
146
+ db.exec(`ALTER TABLE core_chunks ADD COLUMN ${name} ${type}`);
147
+ db.exec(`CREATE INDEX IF NOT EXISTS core_chunks_collection ON core_chunks(collection_id, document_id);
148
+ CREATE INDEX IF NOT EXISTS core_chunks_unindexed ON core_chunks(vector_indexed) WHERE embedding IS NOT NULL;
149
+ CREATE TABLE IF NOT EXISTS core_vector_buckets(dimension INTEGER NOT NULL,band INTEGER NOT NULL,bucket INTEGER NOT NULL,chunk_id TEXT NOT NULL,PRIMARY KEY(dimension,band,bucket,chunk_id));
150
+ CREATE INDEX IF NOT EXISTS core_vector_buckets_chunk ON core_vector_buckets(chunk_id);`);
151
+ db.exec(KNOWLEDGE_TRIGGERS.join(";") + ";");
152
+ if (
153
+ !db
154
+ .prepare("SELECT id FROM core_collections WHERE id='shared-default'")
155
+ .get()
156
+ )
157
+ db.prepare(
158
+ "INSERT INTO core_collections(id,name,scope,created,updated) VALUES ('shared-default','共享知识库','shared',?,?)",
159
+ ).run(Date.now(), Date.now());
160
+ mergeLegacyMemories(db);
161
+ if (
162
+ !db.prepare("SELECT id FROM core_config WHERE id='memory-fts-v2'").get()
163
+ ) {
164
+ db.exec("BEGIN IMMEDIATE");
165
+ try {
166
+ db.exec("DELETE FROM core_memory_fts");
167
+ for (const m of db.prepare("SELECT id,content FROM core_memories").all())
168
+ indexMemory(db, m.id, m.content);
169
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
170
+ "memory-fts-v2",
171
+ JSON.stringify({ time: Date.now() }),
172
+ );
173
+ db.exec("COMMIT");
174
+ } catch (error) {
175
+ db.exec("ROLLBACK");
176
+ throw error;
177
+ }
178
+ }
179
+ if (!db.prepare("SELECT id FROM core_config WHERE id='chunk-fts-v1'").get()) {
180
+ db.exec("BEGIN IMMEDIATE");
181
+ try {
182
+ db.exec("DELETE FROM core_chunk_fts");
183
+ for (const chunk of db
184
+ .prepare("SELECT id,text FROM core_chunks ORDER BY rowid")
185
+ .iterate())
186
+ indexChunk(db, chunk.id, chunk.text);
187
+ db.prepare("INSERT INTO core_config(id,value) VALUES (?,?)").run(
188
+ "chunk-fts-v1",
189
+ JSON.stringify({ time: Date.now() }),
190
+ );
191
+ db.exec("COMMIT");
192
+ } catch (error) {
193
+ db.exec("ROLLBACK");
194
+ throw error;
195
+ }
196
+ }
197
+ }