@wirecat/cli-core 0.434.0 → 0.436.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 (87) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/cli/messenger/context.js +1 -0
  3. package/dist/cli/messenger/conversation-answers.d.ts +1 -0
  4. package/dist/cli/messenger/conversations-command.js +2 -2
  5. package/dist/cli/messenger/message-answers.d.ts +1 -0
  6. package/dist/cli/messenger/message-writes.d.ts +2 -0
  7. package/dist/cli/messenger/messages-search-command.js +6 -10
  8. package/dist/cli/messenger/models-command.js +34 -7
  9. package/dist/cli/messenger/search-answers.d.ts +3 -0
  10. package/dist/cli/messenger/search-answers.js +40 -8
  11. package/dist/cli/messenger/search-command.js +3 -1
  12. package/dist/cli/messenger/store-maintenance-command.js +7 -4
  13. package/dist/cli/model-catalogue-command.js +1 -1
  14. package/dist/cli/profile.d.ts +4 -1
  15. package/dist/cli/profile.js +22 -7
  16. package/dist/cli/program.js +8 -4
  17. package/dist/cli/settings.d.ts +4 -0
  18. package/dist/cli/settings.js +5 -0
  19. package/dist/cli/shared-command-exports.d.ts +4 -0
  20. package/dist/embeddings/child.js +15 -5
  21. package/dist/embeddings/embed.d.ts +16 -2
  22. package/dist/embeddings/embed.js +57 -28
  23. package/dist/embeddings/models.d.ts +18 -0
  24. package/dist/embeddings/models.js +69 -0
  25. package/dist/embeddings/process.d.ts +6 -1
  26. package/dist/embeddings/process.js +23 -7
  27. package/dist/embeddings/rerank.d.ts +10 -0
  28. package/dist/embeddings/rerank.js +31 -0
  29. package/dist/entities/conversation-results.d.ts +2 -0
  30. package/dist/entities/evidence.d.ts +8 -0
  31. package/dist/entities/index.d.ts +27 -1
  32. package/dist/entities/listener.d.ts +4 -0
  33. package/dist/entities/message-context.d.ts +3 -0
  34. package/dist/entities/message-write.d.ts +4 -0
  35. package/dist/entities/message.d.ts +2 -0
  36. package/dist/entities/message.js +2 -0
  37. package/dist/entities/model.d.ts +5 -2
  38. package/dist/entities/model.js +5 -2
  39. package/dist/entities/store-maintenance.d.ts +1 -0
  40. package/dist/entities/store-maintenance.js +8 -1
  41. package/dist/entities/store-records.d.ts +1 -0
  42. package/dist/mcp/tool.d.ts +1 -1
  43. package/dist/mcp/tool.js +1 -0
  44. package/dist/services/deps.d.ts +3 -0
  45. package/dist/services/embeddings-maintenance.d.ts +10 -2
  46. package/dist/services/index.d.ts +2 -0
  47. package/dist/services/index.js +1 -0
  48. package/dist/services/messages-archive-search.d.ts +2 -2
  49. package/dist/services/messages-archive-search.js +4 -4
  50. package/dist/services/messages-discovery.d.ts +33 -4
  51. package/dist/services/messages-discovery.js +88 -3
  52. package/dist/services/messages-meaning.d.ts +2 -1
  53. package/dist/services/messages-meaning.js +7 -3
  54. package/dist/services/messages-rerank.d.ts +7 -0
  55. package/dist/services/messages-rerank.js +42 -0
  56. package/dist/services/messages-search.d.ts +5 -0
  57. package/dist/services/messages.d.ts +6 -2
  58. package/dist/services/messages.js +4 -3
  59. package/dist/services/model-downloads.d.ts +3 -1
  60. package/dist/services/model-downloads.js +20 -1
  61. package/dist/services/notes-search.d.ts +7 -2
  62. package/dist/services/notes-search.js +7 -4
  63. package/dist/services/search-all-meetings.d.ts +3 -2
  64. package/dist/services/search-all-meetings.js +8 -8
  65. package/dist/services/search-all.d.ts +7 -1
  66. package/dist/services/search-all.js +109 -45
  67. package/dist/store/exports.d.ts +1 -1
  68. package/dist/store/exports.js +1 -1
  69. package/dist/store/sqlite/accounts.js +2 -2
  70. package/dist/store/sqlite/chats.d.ts +1 -1
  71. package/dist/store/sqlite/chats.js +11 -4
  72. package/dist/store/sqlite/document-pictures.js +2 -7
  73. package/dist/store/sqlite/email-index.d.ts +1 -3
  74. package/dist/store/sqlite/email-index.js +5 -21
  75. package/dist/store/sqlite/emails.js +4 -3
  76. package/dist/store/sqlite/image-chunks.d.ts +2 -2
  77. package/dist/store/sqlite/image-chunks.js +10 -13
  78. package/dist/store/sqlite/meeting-vectors.js +11 -9
  79. package/dist/store/sqlite/message-chunks.js +3 -8
  80. package/dist/store/sqlite/note-index.js +2 -7
  81. package/dist/store/sqlite/schema.d.ts +1 -1
  82. package/dist/store/sqlite/schema.js +1 -1
  83. package/dist/store/sqlite/vector-index.d.ts +9 -0
  84. package/dist/store/sqlite/vector-index.js +25 -0
  85. package/dist/store/sqlite/vectors.d.ts +2 -2
  86. package/dist/store/sqlite/vectors.js +11 -8
  87. package/package.json +1 -1
@@ -8,6 +8,7 @@ import { indexRow } from "./index-state.js";
8
8
  import { speech } from "./lucene-fields.js";
9
9
  import { savedStemmers } from "./stems.js";
10
10
  import { write } from "./transaction.js";
11
+ import { dropUnusedVectors } from "./vector-index.js";
11
12
  export const CORPORA = {
12
13
  document: {
13
14
  type: "document",
@@ -114,8 +115,6 @@ const writer = (database, corpus, stemmer) => {
114
115
  stems: database.prepare(`INSERT INTO ${corpus.type}_stems (rowid, stems, scope) VALUES (?, ?, ?)`),
115
116
  chunk: database.prepare("INSERT INTO chunks (chunkable_type, chunkable_id, position, start_offset, end_offset, content_hash, scope, account_id, created_at, updated_at) " +
116
117
  "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
117
- purge: database.prepare(`DELETE FROM embeddings WHERE content_hash = ?
118
- AND NOT EXISTS (SELECT 1 FROM chunks WHERE content_hash = ?)`),
119
118
  dequeue: database.prepare(`DELETE FROM ${corpus.type}_index_pending WHERE indexable_type = ? AND id = ?`),
120
119
  };
121
120
  return (id) => {
@@ -134,7 +133,6 @@ const writer = (database, corpus, stemmer) => {
134
133
  const stems = terms ? terms.stems : text ? stemmer.indexText(text) : "";
135
134
  if (stems !== "")
136
135
  statements.stems.run(id, stems, scope);
137
- const after = new Set();
138
136
  if (text.trim() && corpus.pieces !== false) {
139
137
  const at = Date.now();
140
138
  const sections = row?.metadata == null ? [] : storedSections(String(row.metadata));
@@ -143,13 +141,10 @@ const writer = (database, corpus, stemmer) => {
143
141
  : splitText(text, CHUNK_CHARS);
144
142
  ranges.forEach(({ start, end }, position) => {
145
143
  const hash = chunkHash(text.slice(start, end));
146
- after.add(hash);
147
144
  statements.chunk.run(corpus.type, id, position, start, end, hash, scope, int(row?.account_id), at, at);
148
145
  });
149
146
  }
150
- for (const hash of before)
151
- if (!after.has(hash))
152
- statements.purge.run(hash, hash);
147
+ dropUnusedVectors(database, before);
153
148
  if (!live && corpus.type === "document")
154
149
  dropDocumentPicture(database, id);
155
150
  statements.dequeue.run(queuedAs(corpus), id);
@@ -15535,7 +15535,7 @@ export declare const conversationState: import("drizzle-orm/sqlite-core").SQLite
15535
15535
  dialect: 'sqlite';
15536
15536
  isAlias: false;
15537
15537
  }>;
15538
- /** One model's vector for one chunk text; no owner, so vectors outlive the builds and chunks that point at them. */
15538
+ /** One model's vector for one chunk text; no owner: chunks of any kind share it, and it goes once none holds the text. */
15539
15539
  export declare const embeddings: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
15540
15540
  name: "embeddings";
15541
15541
  schema: undefined;
@@ -1556,7 +1556,7 @@ export const conversationState = sqliteTable("conversation_state", {
1556
1556
  /** The build readers see; a higher one is being written, or failed. */
1557
1557
  currentBuild: integer("current_build"),
1558
1558
  });
1559
- /** One model's vector for one chunk text; no owner, so vectors outlive the builds and chunks that point at them. */
1559
+ /** One model's vector for one chunk text; no owner: chunks of any kind share it, and it goes once none holds the text. */
1560
1560
  export const embeddings = sqliteTable("embeddings", {
1561
1561
  /** `<provider>:<model>:<dims>` — vectors of different models never mix. */
1562
1562
  model: text("model").notNull(),
@@ -65,3 +65,12 @@ export declare const vectorIndexCounts: (database: CacheDatabase) => {
65
65
  missing: number;
66
66
  extra: number;
67
67
  }[];
68
+ /**
69
+ * The one rule for a vector whose text may be gone: it goes once no chunk of any kind holds its hash, for every model.
70
+ * Call it with the hashes a write released, after that write's new chunks are in, so a text that comes back keeps its
71
+ * vector. The key is (model, content_hash): naming each model reaches the row by the key; the hash alone scans every
72
+ * vector.
73
+ */
74
+ export declare const dropUnusedVectors: (database: CacheDatabase, hashes: Iterable<string>) => void;
75
+ /** Every vector no chunk holds, left by a build before `dropUnusedVectors` covered every write; how many. */
76
+ export declare const dropAllUnusedVectors: (database: CacheDatabase) => number;
@@ -178,3 +178,28 @@ export const vectorIndexCounts = (database) => database.prepare("SELECT id, mode
178
178
  extra: count(`${held} EXCEPT ${wanted}`),
179
179
  };
180
180
  });
181
+ /**
182
+ * The one rule for a vector whose text may be gone: it goes once no chunk of any kind holds its hash, for every model.
183
+ * Call it with the hashes a write released, after that write's new chunks are in, so a text that comes back keeps its
184
+ * vector. The key is (model, content_hash): naming each model reaches the row by the key; the hash alone scans every
185
+ * vector.
186
+ */
187
+ export const dropUnusedVectors = (database, hashes) => {
188
+ const unique = new Set(hashes);
189
+ if (unique.size === 0)
190
+ return;
191
+ const models = database
192
+ .prepare(`WITH RECURSIVE m(model) AS (SELECT min(model) FROM embeddings
193
+ UNION ALL SELECT (SELECT min(model) FROM embeddings WHERE model > m.model) FROM m WHERE m.model IS NOT NULL)
194
+ SELECT model FROM m WHERE model IS NOT NULL`)
195
+ .all()
196
+ .map(({ model }) => String(model));
197
+ const drop = database.prepare("DELETE FROM embeddings WHERE model = ? AND content_hash = ? AND NOT EXISTS (SELECT 1 FROM chunks WHERE content_hash = ?)");
198
+ for (const hash of unique)
199
+ for (const model of models)
200
+ drop.run(model, hash, hash);
201
+ };
202
+ /** Every vector no chunk holds, left by a build before `dropUnusedVectors` covered every write; how many. */
203
+ export const dropAllUnusedVectors = (database) => database
204
+ .prepare("DELETE FROM embeddings WHERE NOT EXISTS (SELECT 1 FROM chunks k WHERE k.content_hash = embeddings.content_hash) RETURNING 1")
205
+ .all().length;
@@ -50,8 +50,8 @@ export declare const vectorStatus: ({ orm }: StoreContext, chatKey: number, mode
50
50
  embedded: number;
51
51
  };
52
52
  /**
53
- * Drops the vectors of the chat's chunks, or only one model's — but not one another chat's chunk still
54
- * points at: the same text in two chats has one vector. Messages are never touched.
53
+ * Drops the vectors of the chat's conversation and message chunks, or only one model's — but not one a chunk outside
54
+ * the chat still points at: the same text in two chats has one vector. The chunks themselves stay, to be embedded again.
55
55
  */
56
56
  export declare const clearVectors: ({ orm }: StoreContext, chatKey: number, model: string | undefined) => number;
57
57
  export interface NearestChunk {
@@ -138,16 +138,19 @@ export const vectorStatus = ({ orm }, chatKey, model) => {
138
138
  return { chunks: Number(row?.chunks ?? 0), embedded: Number(row?.embedded ?? 0) };
139
139
  };
140
140
  /**
141
- * Drops the vectors of the chat's chunks, or only one model's — but not one another chat's chunk still
142
- * points at: the same text in two chats has one vector. Messages are never touched.
141
+ * Drops the vectors of the chat's conversation and message chunks, or only one model's — but not one a chunk outside
142
+ * the chat still points at: the same text in two chats has one vector. The chunks themselves stay, to be embedded again.
143
143
  */
144
- export const clearVectors = ({ orm }, chatKey, model) => orm.all(sql `DELETE FROM embeddings
144
+ export const clearVectors = ({ orm }, chatKey, model) => orm.all(sql `WITH mine(id) AS (
145
+ SELECT k.id FROM chunks k JOIN conversations c ON k.chunkable_type = 'conversation' AND c.id = k.chunkable_id
146
+ WHERE c.chat_id = ${chatKey}
147
+ UNION ALL SELECT k.id FROM chunks k JOIN messages m ON k.chunkable_type = 'message' AND m.id = k.chunkable_id
148
+ WHERE m.chat_id = ${chatKey})
149
+ DELETE FROM embeddings
145
150
  WHERE ${model === undefined ? sql `1` : sql `model = ${model}`}
146
- AND content_hash IN (SELECT k.content_hash FROM chunks k
147
- JOIN conversations c ON k.chunkable_type = 'conversation' AND c.id = k.chunkable_id WHERE c.chat_id = ${chatKey})
148
- AND content_hash NOT IN (SELECT k.content_hash FROM chunks k
149
- LEFT JOIN conversations c ON k.chunkable_type = 'conversation' AND c.id = k.chunkable_id
150
- WHERE c.id IS NULL OR c.chat_id <> ${chatKey})
151
+ AND content_hash IN (SELECT content_hash FROM chunks WHERE id IN (SELECT id FROM mine))
152
+ AND NOT EXISTS (SELECT 1 FROM chunks o WHERE o.content_hash = embeddings.content_hash
153
+ AND o.id NOT IN (SELECT id FROM mine))
151
154
  RETURNING 1 AS n`).length;
152
155
  /** Rows read per step of a search, so a chat of any size is scanned in bounded memory. */
153
156
  const SCAN_PAGE = 5_000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.434.0",
3
+ "version": "0.436.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",