@memtensor/memos-local-openclaw-plugin 1.0.5 → 1.0.6-beta.2

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 (60) hide show
  1. package/dist/capture/index.d.ts.map +1 -1
  2. package/dist/capture/index.js +24 -0
  3. package/dist/capture/index.js.map +1 -1
  4. package/dist/client/connector.d.ts.map +1 -1
  5. package/dist/client/connector.js +33 -5
  6. package/dist/client/connector.js.map +1 -1
  7. package/dist/client/hub.d.ts.map +1 -1
  8. package/dist/client/hub.js +4 -0
  9. package/dist/client/hub.js.map +1 -1
  10. package/dist/hub/server.d.ts +2 -0
  11. package/dist/hub/server.d.ts.map +1 -1
  12. package/dist/hub/server.js +116 -54
  13. package/dist/hub/server.js.map +1 -1
  14. package/dist/ingest/providers/index.d.ts +4 -0
  15. package/dist/ingest/providers/index.d.ts.map +1 -1
  16. package/dist/ingest/providers/index.js +32 -86
  17. package/dist/ingest/providers/index.js.map +1 -1
  18. package/dist/ingest/providers/openai.d.ts.map +1 -1
  19. package/dist/ingest/providers/openai.js +29 -13
  20. package/dist/ingest/providers/openai.js.map +1 -1
  21. package/dist/recall/engine.d.ts.map +1 -1
  22. package/dist/recall/engine.js +33 -32
  23. package/dist/recall/engine.js.map +1 -1
  24. package/dist/storage/sqlite.d.ts +43 -7
  25. package/dist/storage/sqlite.d.ts.map +1 -1
  26. package/dist/storage/sqlite.js +179 -58
  27. package/dist/storage/sqlite.js.map +1 -1
  28. package/dist/tools/memory-get.d.ts.map +1 -1
  29. package/dist/tools/memory-get.js +4 -1
  30. package/dist/tools/memory-get.js.map +1 -1
  31. package/dist/types.d.ts +1 -1
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js.map +1 -1
  34. package/dist/viewer/html.d.ts.map +1 -1
  35. package/dist/viewer/html.js +71 -21
  36. package/dist/viewer/html.js.map +1 -1
  37. package/dist/viewer/server.d.ts +24 -0
  38. package/dist/viewer/server.d.ts.map +1 -1
  39. package/dist/viewer/server.js +398 -144
  40. package/dist/viewer/server.js.map +1 -1
  41. package/index.ts +86 -34
  42. package/package.json +1 -1
  43. package/scripts/postinstall.cjs +21 -5
  44. package/src/capture/index.ts +36 -0
  45. package/src/client/connector.ts +32 -5
  46. package/src/client/hub.ts +4 -0
  47. package/src/hub/server.ts +110 -50
  48. package/src/ingest/providers/index.ts +37 -92
  49. package/src/ingest/providers/openai.ts +31 -13
  50. package/src/recall/engine.ts +32 -30
  51. package/src/storage/sqlite.ts +196 -63
  52. package/src/tools/memory-get.ts +4 -1
  53. package/src/types.ts +2 -0
  54. package/src/viewer/html.ts +71 -21
  55. package/src/viewer/server.ts +387 -139
  56. package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
  57. package/prebuilds/darwin-x64/better_sqlite3.node +0 -0
  58. package/prebuilds/linux-x64/better_sqlite3.node +0 -0
  59. package/prebuilds/win32-x64/better_sqlite3.node +0 -0
  60. package/telemetry.credentials.json +0 -5
@@ -62,10 +62,20 @@ export class RecallEngine {
62
62
 
63
63
  // Step 1b: Pattern search (LIKE-based) as fallback for short terms that
64
64
  // trigram FTS cannot match (trigram requires >= 3 chars).
65
- const shortTerms = query
66
- .replace(/[."""(){}[\]*:^~!@#$%&\\/<>,;'`??。,!、:""''()【】《》]/g, " ")
67
- .split(/\s+/)
68
- .filter((t) => t.length === 2);
65
+ // For CJK text without spaces, extract bigrams (2-char sliding windows)
66
+ // so that queries like "唐波是谁" produce ["唐波", "波是", "是谁"].
67
+ const cleaned = query.replace(/[."""(){}[\]*:^~!@#$%&\\/<>,;'`??。,!、:""''()【】《》]/g, " ");
68
+ const spaceSplit = cleaned.split(/\s+/).filter((t) => t.length === 2);
69
+ const cjkBigrams: string[] = [];
70
+ const cjkRuns = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uF900-\uFAFF]{2,}/g);
71
+ if (cjkRuns) {
72
+ for (const run of cjkRuns) {
73
+ for (let i = 0; i <= run.length - 2; i++) {
74
+ cjkBigrams.push(run.slice(i, i + 2));
75
+ }
76
+ }
77
+ }
78
+ const shortTerms = [...new Set([...spaceSplit, ...cjkBigrams])];
69
79
  const patternHits = shortTerms.length > 0
70
80
  ? this.store.patternSearch(shortTerms, { limit: candidatePool })
71
81
  : [];
@@ -74,49 +84,41 @@ export class RecallEngine {
74
84
  score: 1 / (i + 1),
75
85
  }));
76
86
 
77
- // Step 1c: Hub memories search only in Hub mode where local DB owns the
78
- // hub_memories data and embeddings were generated by the same Embedder.
79
- // Client mode must use remote API (hubSearchMemories) to avoid cross-model
80
- // embedding mismatch.
87
+ // Step 1c: Hub memories — FTS + pattern + cached embeddings (same strategy as chunks/skills).
81
88
  let hubMemFtsRanked: Array<{ id: string; score: number }> = [];
82
89
  let hubMemVecRanked: Array<{ id: string; score: number }> = [];
83
90
  let hubMemPatternRanked: Array<{ id: string; score: number }> = [];
84
91
  if (query && this.ctx.config.sharing?.enabled && this.ctx.config.sharing.role === "hub") {
85
92
  try {
86
93
  const hubFtsHits = this.store.searchHubMemories(query, { maxResults: candidatePool });
87
- hubMemFtsRanked = hubFtsHits.map(({ hit }, i) => ({
88
- id: `hubmem:${hit.id}`, score: 1 / (i + 1),
89
- }));
94
+ hubMemFtsRanked = hubFtsHits.map(({ hit }, i) => ({ id: `hubmem:${hit.id}`, score: 1 / (i + 1) }));
90
95
  } catch { /* hub_memories table may not exist */ }
91
96
  if (shortTerms.length > 0) {
92
97
  try {
93
98
  const hubPatternHits = this.store.hubMemoryPatternSearch(shortTerms, { limit: candidatePool });
94
- hubMemPatternRanked = hubPatternHits.map((h, i) => ({
95
- id: `hubmem:${h.memoryId}`, score: 1 / (i + 1),
96
- }));
99
+ hubMemPatternRanked = hubPatternHits.map((h, i) => ({ id: `hubmem:${h.memoryId}`, score: 1 / (i + 1) }));
97
100
  } catch { /* best-effort */ }
98
101
  }
102
+
99
103
  try {
100
- const hubMemEmbs = this.store.getVisibleHubMemoryEmbeddings("");
101
- if (hubMemEmbs.length > 0) {
102
- const qv = await this.embedder.embedQuery(query).catch(() => null);
103
- if (qv) {
104
- const scored: Array<{ id: string; score: number }> = [];
105
- for (const e of hubMemEmbs) {
106
- let dot = 0, nA = 0, nB = 0;
107
- for (let i = 0; i < qv.length && i < e.vector.length; i++) {
108
- dot += qv[i] * e.vector[i]; nA += qv[i] * qv[i]; nB += e.vector[i] * e.vector[i];
109
- }
110
- const sim = nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
111
- if (sim > 0.3) {
112
- scored.push({ id: `hubmem:${e.memoryId}`, score: sim });
113
- }
104
+ const qv = await this.embedder.embedQuery(query).catch(() => null);
105
+ if (qv) {
106
+ const memEmbs = this.store.getVisibleHubMemoryEmbeddings("__hub__");
107
+ const scored: Array<{ id: string; score: number }> = [];
108
+ for (const e of memEmbs) {
109
+ let dot = 0, nA = 0, nB = 0;
110
+ const len = Math.min(qv.length, e.vector.length);
111
+ for (let i = 0; i < len; i++) {
112
+ dot += qv[i] * e.vector[i]; nA += qv[i] * qv[i]; nB += e.vector[i] * e.vector[i];
114
113
  }
115
- scored.sort((a, b) => b.score - a.score);
116
- hubMemVecRanked = scored.slice(0, candidatePool);
114
+ const sim = nA > 0 && nB > 0 ? dot / (Math.sqrt(nA) * Math.sqrt(nB)) : 0;
115
+ if (sim > 0.3) scored.push({ id: `hubmem:${e.memoryId}`, score: sim });
117
116
  }
117
+ scored.sort((a, b) => b.score - a.score);
118
+ hubMemVecRanked = scored.slice(0, candidatePool);
118
119
  }
119
120
  } catch { /* best-effort */ }
121
+
120
122
  const hubTotal = hubMemFtsRanked.length + hubMemVecRanked.length + hubMemPatternRanked.length;
121
123
  if (hubTotal > 0) {
122
124
  this.ctx.log.debug(`recall: hub_memories candidates: fts=${hubMemFtsRanked.length}, vec=${hubMemVecRanked.length}, pattern=${hubMemPatternRanked.length}`);
@@ -116,6 +116,7 @@ export class SqliteStore {
116
116
  this.migrateLocalSharedTasksOwner();
117
117
  this.migrateHubUserIdentityFields();
118
118
  this.migrateClientHubConnectionIdentityFields();
119
+ this.migrateTeamSharingInstanceId();
119
120
  this.log.debug("Database schema initialized");
120
121
  }
121
122
 
@@ -176,6 +177,40 @@ export class SqliteStore {
176
177
  } catch { /* table may not exist yet */ }
177
178
  }
178
179
 
180
+ private migrateTeamSharingInstanceId(): void {
181
+ try {
182
+ const tscCols = this.db.prepare("PRAGMA table_info(team_shared_chunks)").all() as Array<{ name: string }>;
183
+ if (tscCols.length > 0 && !tscCols.some(c => c.name === "hub_instance_id")) {
184
+ this.db.exec("ALTER TABLE team_shared_chunks ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
185
+ this.log.info("Migrated: added hub_instance_id to team_shared_chunks");
186
+ }
187
+ } catch { /* table may not exist yet */ }
188
+ try {
189
+ const lstCols = this.db.prepare("PRAGMA table_info(local_shared_tasks)").all() as Array<{ name: string }>;
190
+ if (lstCols.length > 0 && !lstCols.some(c => c.name === "hub_instance_id")) {
191
+ this.db.exec("ALTER TABLE local_shared_tasks ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
192
+ this.log.info("Migrated: added hub_instance_id to local_shared_tasks");
193
+ }
194
+ } catch { /* table may not exist yet */ }
195
+ try {
196
+ const connCols = this.db.prepare("PRAGMA table_info(client_hub_connection)").all() as Array<{ name: string }>;
197
+ if (connCols.length > 0 && !connCols.some(c => c.name === "hub_instance_id")) {
198
+ this.db.exec("ALTER TABLE client_hub_connection ADD COLUMN hub_instance_id TEXT NOT NULL DEFAULT ''");
199
+ this.log.info("Migrated: added hub_instance_id to client_hub_connection");
200
+ }
201
+ } catch { /* table may not exist yet */ }
202
+ this.db.exec(`
203
+ CREATE TABLE IF NOT EXISTS team_shared_skills (
204
+ skill_id TEXT PRIMARY KEY,
205
+ hub_skill_id TEXT NOT NULL DEFAULT '',
206
+ visibility TEXT NOT NULL DEFAULT 'public',
207
+ group_id TEXT,
208
+ hub_instance_id TEXT NOT NULL DEFAULT '',
209
+ shared_at INTEGER NOT NULL
210
+ )
211
+ `);
212
+ }
213
+
179
214
  private migrateOwnerFields(): void {
180
215
  const chunkCols = this.db.prepare("PRAGMA table_info(chunks)").all() as Array<{ name: string }>;
181
216
  if (!chunkCols.some((c) => c.name === "owner")) {
@@ -778,12 +813,13 @@ export class SqliteStore {
778
813
  );
779
814
 
780
815
  CREATE TABLE IF NOT EXISTS local_shared_tasks (
781
- task_id TEXT PRIMARY KEY,
782
- hub_task_id TEXT NOT NULL,
783
- visibility TEXT NOT NULL DEFAULT 'public',
784
- group_id TEXT,
785
- synced_chunks INTEGER NOT NULL DEFAULT 0,
786
- shared_at INTEGER NOT NULL
816
+ task_id TEXT PRIMARY KEY,
817
+ hub_task_id TEXT NOT NULL,
818
+ visibility TEXT NOT NULL DEFAULT 'public',
819
+ group_id TEXT,
820
+ synced_chunks INTEGER NOT NULL DEFAULT 0,
821
+ hub_instance_id TEXT NOT NULL DEFAULT '',
822
+ shared_at INTEGER NOT NULL
787
823
  );
788
824
 
789
825
  CREATE TABLE IF NOT EXISTS local_shared_memories (
@@ -794,11 +830,21 @@ export class SqliteStore {
794
830
 
795
831
  -- Client: team share UI metadata only (no hub_memories row — avoids local FTS/embed recall duplication)
796
832
  CREATE TABLE IF NOT EXISTS team_shared_chunks (
797
- chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
798
- hub_memory_id TEXT NOT NULL DEFAULT '',
799
- visibility TEXT NOT NULL DEFAULT 'public',
800
- group_id TEXT,
801
- shared_at INTEGER NOT NULL
833
+ chunk_id TEXT PRIMARY KEY REFERENCES chunks(id) ON DELETE CASCADE,
834
+ hub_memory_id TEXT NOT NULL DEFAULT '',
835
+ visibility TEXT NOT NULL DEFAULT 'public',
836
+ group_id TEXT,
837
+ hub_instance_id TEXT NOT NULL DEFAULT '',
838
+ shared_at INTEGER NOT NULL
839
+ );
840
+
841
+ CREATE TABLE IF NOT EXISTS team_shared_skills (
842
+ skill_id TEXT PRIMARY KEY,
843
+ hub_skill_id TEXT NOT NULL DEFAULT '',
844
+ visibility TEXT NOT NULL DEFAULT 'public',
845
+ group_id TEXT,
846
+ hub_instance_id TEXT NOT NULL DEFAULT '',
847
+ shared_at INTEGER NOT NULL
802
848
  );
803
849
 
804
850
  CREATE TABLE IF NOT EXISTS hub_users (
@@ -960,10 +1006,10 @@ export class SqliteStore {
960
1006
  CREATE INDEX IF NOT EXISTS idx_hub_memories_group ON hub_memories(group_id);
961
1007
 
962
1008
  CREATE TABLE IF NOT EXISTS hub_memory_embeddings (
963
- memory_id TEXT PRIMARY KEY REFERENCES hub_memories(id) ON DELETE CASCADE,
964
- vector BLOB NOT NULL,
965
- dimensions INTEGER NOT NULL,
966
- updated_at INTEGER NOT NULL
1009
+ memory_id TEXT PRIMARY KEY REFERENCES hub_memories(id) ON DELETE CASCADE,
1010
+ vector BLOB NOT NULL,
1011
+ dimensions INTEGER NOT NULL,
1012
+ updated_at INTEGER NOT NULL
967
1013
  );
968
1014
 
969
1015
  CREATE VIRTUAL TABLE IF NOT EXISTS hub_memories_fts USING fts5(
@@ -1211,6 +1257,13 @@ export class SqliteStore {
1211
1257
  } catch { return []; }
1212
1258
  }
1213
1259
 
1260
+ listHubMemories(opts: { limit?: number } = {}): Array<{ id: string; summary?: string; content?: string }> {
1261
+ const limit = opts.limit ?? 200;
1262
+ try {
1263
+ return this.db.prepare("SELECT id, summary, content FROM hub_memories ORDER BY created_at DESC LIMIT ?").all(limit) as Array<{ id: string; summary?: string; content?: string }>;
1264
+ } catch { return []; }
1265
+ }
1266
+
1214
1267
  // ─── Vector Search ───
1215
1268
 
1216
1269
  getAllEmbeddings(ownerFilter?: string[]): Array<{ chunkId: string; vector: number[] }> {
@@ -1379,6 +1432,7 @@ export class SqliteStore {
1379
1432
  "skills",
1380
1433
  "local_shared_memories",
1381
1434
  "team_shared_chunks",
1435
+ "team_shared_skills",
1382
1436
  "local_shared_tasks",
1383
1437
  "embeddings",
1384
1438
  "chunks",
@@ -1803,8 +1857,8 @@ export class SqliteStore {
1803
1857
 
1804
1858
  setClientHubConnection(conn: ClientHubConnection): void {
1805
1859
  this.db.prepare(`
1806
- INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status)
1807
- VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?)
1860
+ INSERT INTO client_hub_connection (id, hub_url, user_id, username, user_token, role, connected_at, identity_key, last_known_status, hub_instance_id)
1861
+ VALUES (1, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1808
1862
  ON CONFLICT(id) DO UPDATE SET
1809
1863
  hub_url = excluded.hub_url,
1810
1864
  user_id = excluded.user_id,
@@ -1813,8 +1867,9 @@ export class SqliteStore {
1813
1867
  role = excluded.role,
1814
1868
  connected_at = excluded.connected_at,
1815
1869
  identity_key = excluded.identity_key,
1816
- last_known_status = excluded.last_known_status
1817
- `).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "");
1870
+ last_known_status = excluded.last_known_status,
1871
+ hub_instance_id = excluded.hub_instance_id
1872
+ `).run(conn.hubUrl, conn.userId, conn.username, conn.userToken, conn.role, conn.connectedAt, conn.identityKey ?? "", conn.lastKnownStatus ?? "", conn.hubInstanceId ?? "");
1818
1873
  }
1819
1874
 
1820
1875
  getClientHubConnection(): ClientHubConnection | null {
@@ -1828,32 +1883,33 @@ export class SqliteStore {
1828
1883
 
1829
1884
  // ─── Local Shared Tasks (client-side tracking) ───
1830
1885
 
1831
- markTaskShared(taskId: string, hubTaskId: string, syncedChunks: number, visibility: string, groupId?: string | null): void {
1886
+ markTaskShared(taskId: string, hubTaskId: string, syncedChunks: number, visibility: string, groupId?: string | null, hubInstanceId?: string): void {
1832
1887
  this.db.prepare(`
1833
- INSERT INTO local_shared_tasks (task_id, hub_task_id, visibility, group_id, synced_chunks, shared_at)
1834
- VALUES (?, ?, ?, ?, ?, ?)
1888
+ INSERT INTO local_shared_tasks (task_id, hub_task_id, visibility, group_id, synced_chunks, hub_instance_id, shared_at)
1889
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1835
1890
  ON CONFLICT(task_id) DO UPDATE SET
1836
1891
  hub_task_id = excluded.hub_task_id,
1837
1892
  visibility = excluded.visibility,
1838
1893
  group_id = excluded.group_id,
1839
1894
  synced_chunks = excluded.synced_chunks,
1895
+ hub_instance_id = excluded.hub_instance_id,
1840
1896
  shared_at = excluded.shared_at
1841
- `).run(taskId, hubTaskId, visibility, groupId ?? null, syncedChunks, Date.now());
1897
+ `).run(taskId, hubTaskId, visibility, groupId ?? null, syncedChunks, hubInstanceId ?? "", Date.now());
1842
1898
  }
1843
1899
 
1844
1900
  unmarkTaskShared(taskId: string): void {
1845
1901
  this.db.prepare('DELETE FROM local_shared_tasks WHERE task_id = ?').run(taskId);
1846
1902
  }
1847
1903
 
1848
- getLocalSharedTask(taskId: string): { taskId: string; hubTaskId: string; visibility: string; groupId: string | null; syncedChunks: number; sharedAt: number } | null {
1904
+ getLocalSharedTask(taskId: string): { taskId: string; hubTaskId: string; visibility: string; groupId: string | null; syncedChunks: number; sharedAt: number; hubInstanceId: string } | null {
1849
1905
  const row = this.db.prepare('SELECT * FROM local_shared_tasks WHERE task_id = ?').get(taskId) as any;
1850
1906
  if (!row) return null;
1851
- return { taskId: row.task_id, hubTaskId: row.hub_task_id, visibility: row.visibility, groupId: row.group_id, syncedChunks: row.synced_chunks, sharedAt: row.shared_at };
1907
+ return { taskId: row.task_id, hubTaskId: row.hub_task_id, visibility: row.visibility, groupId: row.group_id, syncedChunks: row.synced_chunks, sharedAt: row.shared_at, hubInstanceId: row.hub_instance_id || "" };
1852
1908
  }
1853
1909
 
1854
- listLocalSharedTasks(): Array<{ taskId: string; hubTaskId: string; visibility: string; groupId: string | null; syncedChunks: number }> {
1855
- const rows = this.db.prepare('SELECT task_id, hub_task_id, visibility, group_id, synced_chunks FROM local_shared_tasks').all() as any[];
1856
- return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks }));
1910
+ listLocalSharedTasks(): Array<{ taskId: string; hubTaskId: string; visibility: string; groupId: string | null; syncedChunks: number; hubInstanceId: string }> {
1911
+ const rows = this.db.prepare('SELECT task_id, hub_task_id, visibility, group_id, synced_chunks, hub_instance_id FROM local_shared_tasks').all() as any[];
1912
+ return rows.map(r => ({ taskId: r.task_id, hubTaskId: r.hub_task_id, visibility: r.visibility, groupId: r.group_id, syncedChunks: r.synced_chunks, hubInstanceId: r.hub_instance_id || "" }));
1857
1913
  }
1858
1914
 
1859
1915
  // ─── Local Shared Memories (client-side tracking) ───
@@ -1958,11 +2014,23 @@ export class SqliteStore {
1958
2014
  });
1959
2015
  }
1960
2016
 
2017
+ deleteHubMemoriesByUser(userId: string): void {
2018
+ this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
2019
+ }
2020
+
2021
+ deleteHubTasksByUser(userId: string): void {
2022
+ this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
2023
+ }
2024
+
2025
+ deleteHubSkillsByUser(userId: string): void {
2026
+ this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
2027
+ }
2028
+
1961
2029
  deleteHubUser(userId: string, cleanResources = false): boolean {
1962
2030
  if (cleanResources) {
1963
- this.db.prepare('DELETE FROM hub_tasks WHERE source_user_id = ?').run(userId);
1964
- this.db.prepare('DELETE FROM hub_skills WHERE source_user_id = ?').run(userId);
1965
- this.db.prepare('DELETE FROM hub_memories WHERE source_user_id = ?').run(userId);
2031
+ this.deleteHubTasksByUser(userId);
2032
+ this.deleteHubSkillsByUser(userId);
2033
+ this.deleteHubMemoriesByUser(userId);
1966
2034
  const result = this.db.prepare('DELETE FROM hub_users WHERE id = ?').run(userId);
1967
2035
  return result.changes > 0;
1968
2036
  }
@@ -2138,6 +2206,36 @@ export class SqliteStore {
2138
2206
  }));
2139
2207
  }
2140
2208
 
2209
+ upsertHubMemoryEmbedding(memoryId: string, vector: Float32Array): void {
2210
+ const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
2211
+ this.db.prepare(`
2212
+ INSERT INTO hub_memory_embeddings (memory_id, vector, dimensions, updated_at)
2213
+ VALUES (?, ?, ?, ?)
2214
+ ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, dimensions = excluded.dimensions, updated_at = excluded.updated_at
2215
+ `).run(memoryId, buf, vector.length, Date.now());
2216
+ }
2217
+
2218
+ getHubMemoryEmbedding(memoryId: string): Float32Array | null {
2219
+ const row = this.db.prepare('SELECT vector, dimensions FROM hub_memory_embeddings WHERE memory_id = ?').get(memoryId) as { vector: Buffer; dimensions: number } | undefined;
2220
+ if (!row) return null;
2221
+ return new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions);
2222
+ }
2223
+
2224
+ getVisibleHubMemoryEmbeddings(userId: string): Array<{ memoryId: string; vector: Float32Array }> {
2225
+ const rows = this.db.prepare(`
2226
+ SELECT hme.memory_id, hme.vector, hme.dimensions
2227
+ FROM hub_memory_embeddings hme
2228
+ JOIN hub_memories hm ON hm.id = hme.memory_id
2229
+ WHERE hm.visibility = 'public'
2230
+ OR hm.source_user_id = ?
2231
+ OR EXISTS (SELECT 1 FROM hub_group_members gm WHERE gm.group_id = hm.group_id AND gm.user_id = ?)
2232
+ `).all(userId, userId) as Array<{ memory_id: string; vector: Buffer; dimensions: number }>;
2233
+ return rows.map(r => ({
2234
+ memoryId: r.memory_id,
2235
+ vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
2236
+ }));
2237
+ }
2238
+
2141
2239
  searchHubChunks(query: string, options?: { userId?: string; maxResults?: number }): Array<{ hit: HubSearchRow; rank: number }> {
2142
2240
  const limit = options?.maxResults ?? 10;
2143
2241
  const userId = options?.userId ?? "";
@@ -2369,25 +2467,26 @@ export class SqliteStore {
2369
2467
 
2370
2468
  upsertTeamSharedChunk(
2371
2469
  chunkId: string,
2372
- row: { hubMemoryId?: string; visibility?: string; groupId?: string | null },
2470
+ row: { hubMemoryId?: string; visibility?: string; groupId?: string | null; hubInstanceId?: string },
2373
2471
  ): void {
2374
2472
  const now = Date.now();
2375
2473
  const vis = row.visibility === "group" ? "group" : "public";
2376
2474
  const gid = vis === "group" ? (row.groupId ?? null) : null;
2377
2475
  this.db.prepare(`
2378
- INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, shared_at)
2379
- VALUES (?, ?, ?, ?, ?)
2476
+ INSERT INTO team_shared_chunks (chunk_id, hub_memory_id, visibility, group_id, hub_instance_id, shared_at)
2477
+ VALUES (?, ?, ?, ?, ?, ?)
2380
2478
  ON CONFLICT(chunk_id) DO UPDATE SET
2381
2479
  hub_memory_id = excluded.hub_memory_id,
2382
2480
  visibility = excluded.visibility,
2383
2481
  group_id = excluded.group_id,
2482
+ hub_instance_id = excluded.hub_instance_id,
2384
2483
  shared_at = excluded.shared_at
2385
- `).run(chunkId, row.hubMemoryId ?? "", vis, gid, now);
2484
+ `).run(chunkId, row.hubMemoryId ?? "", vis, gid, row.hubInstanceId ?? "", now);
2386
2485
  }
2387
2486
 
2388
- getTeamSharedChunk(chunkId: string): { chunkId: string; hubMemoryId: string; visibility: string; groupId: string | null; sharedAt: number } | null {
2389
- const r = this.db.prepare("SELECT chunk_id, hub_memory_id, visibility, group_id, shared_at FROM team_shared_chunks WHERE chunk_id = ?").get(chunkId) as {
2390
- chunk_id: string; hub_memory_id: string; visibility: string; group_id: string | null; shared_at: number;
2487
+ getTeamSharedChunk(chunkId: string): { chunkId: string; hubMemoryId: string; visibility: string; groupId: string | null; hubInstanceId: string; sharedAt: number } | null {
2488
+ const r = this.db.prepare("SELECT chunk_id, hub_memory_id, visibility, group_id, hub_instance_id, shared_at FROM team_shared_chunks WHERE chunk_id = ?").get(chunkId) as {
2489
+ chunk_id: string; hub_memory_id: string; visibility: string; group_id: string | null; hub_instance_id: string; shared_at: number;
2391
2490
  } | undefined;
2392
2491
  if (!r) return null;
2393
2492
  return {
@@ -2395,6 +2494,7 @@ export class SqliteStore {
2395
2494
  hubMemoryId: r.hub_memory_id,
2396
2495
  visibility: r.visibility,
2397
2496
  groupId: r.group_id,
2497
+ hubInstanceId: r.hub_instance_id || "",
2398
2498
  sharedAt: r.shared_at,
2399
2499
  };
2400
2500
  }
@@ -2404,6 +2504,58 @@ export class SqliteStore {
2404
2504
  return info.changes > 0;
2405
2505
  }
2406
2506
 
2507
+ // ─── Team Shared Skills (Client role — UI metadata only) ───
2508
+
2509
+ upsertTeamSharedSkill(skillId: string, row: { hubSkillId?: string; visibility?: string; groupId?: string | null; hubInstanceId?: string }): void {
2510
+ const now = Date.now();
2511
+ const vis = row.visibility === "group" ? "group" : "public";
2512
+ const gid = vis === "group" ? (row.groupId ?? null) : null;
2513
+ this.db.prepare(`
2514
+ INSERT INTO team_shared_skills (skill_id, hub_skill_id, visibility, group_id, hub_instance_id, shared_at)
2515
+ VALUES (?, ?, ?, ?, ?, ?)
2516
+ ON CONFLICT(skill_id) DO UPDATE SET
2517
+ hub_skill_id = excluded.hub_skill_id,
2518
+ visibility = excluded.visibility,
2519
+ group_id = excluded.group_id,
2520
+ hub_instance_id = excluded.hub_instance_id,
2521
+ shared_at = excluded.shared_at
2522
+ `).run(skillId, row.hubSkillId ?? "", vis, gid, row.hubInstanceId ?? "", now);
2523
+ }
2524
+
2525
+ getTeamSharedSkill(skillId: string): { skillId: string; hubSkillId: string; visibility: string; groupId: string | null; hubInstanceId: string; sharedAt: number } | null {
2526
+ const r = this.db.prepare("SELECT * FROM team_shared_skills WHERE skill_id = ?").get(skillId) as any;
2527
+ if (!r) return null;
2528
+ return { skillId: r.skill_id, hubSkillId: r.hub_skill_id, visibility: r.visibility, groupId: r.group_id, hubInstanceId: r.hub_instance_id || "", sharedAt: r.shared_at };
2529
+ }
2530
+
2531
+ deleteTeamSharedSkill(skillId: string): boolean {
2532
+ return this.db.prepare("DELETE FROM team_shared_skills WHERE skill_id = ?").run(skillId).changes > 0;
2533
+ }
2534
+
2535
+ // ─── Team sharing cleanup (role switch / leave) ───
2536
+
2537
+ clearTeamSharedChunks(): void {
2538
+ this.db.prepare("DELETE FROM team_shared_chunks").run();
2539
+ }
2540
+
2541
+ clearTeamSharedSkills(): void {
2542
+ this.db.prepare("DELETE FROM team_shared_skills").run();
2543
+ }
2544
+
2545
+ downgradeTeamSharedTasksToLocal(): void {
2546
+ this.db.prepare("UPDATE local_shared_tasks SET hub_task_id = '', hub_instance_id = '', visibility = 'public', group_id = NULL, synced_chunks = 0").run();
2547
+ }
2548
+
2549
+ downgradeTeamSharedTaskToLocal(taskId: string): void {
2550
+ this.db.prepare("UPDATE local_shared_tasks SET hub_task_id = '', hub_instance_id = '', visibility = 'public', group_id = NULL, synced_chunks = 0 WHERE task_id = ?").run(taskId);
2551
+ }
2552
+
2553
+ clearAllTeamSharingState(): void {
2554
+ this.clearTeamSharedChunks();
2555
+ this.clearTeamSharedSkills();
2556
+ this.downgradeTeamSharedTasksToLocal();
2557
+ }
2558
+
2407
2559
  // ─── Hub Notifications ───
2408
2560
 
2409
2561
  insertHubNotification(n: { id: string; userId: string; type: string; resource: string; title: string; message?: string }): void {
@@ -2445,20 +2597,8 @@ export class SqliteStore {
2445
2597
  this.db.prepare('DELETE FROM hub_notifications WHERE user_id = ?').run(userId);
2446
2598
  }
2447
2599
 
2448
- upsertHubMemoryEmbedding(memoryId: string, vector: Float32Array): void {
2449
- const buf = Buffer.from(vector.buffer, vector.byteOffset, vector.byteLength);
2450
- this.db.prepare(`
2451
- INSERT INTO hub_memory_embeddings (memory_id, vector, dimensions, updated_at)
2452
- VALUES (?, ?, ?, ?)
2453
- ON CONFLICT(memory_id) DO UPDATE SET vector = excluded.vector, dimensions = excluded.dimensions, updated_at = excluded.updated_at
2454
- `).run(memoryId, buf, vector.length, Date.now());
2455
- }
2456
-
2457
- getHubMemoryEmbedding(memoryId: string): Float32Array | null {
2458
- const row = this.db.prepare('SELECT vector, dimensions FROM hub_memory_embeddings WHERE memory_id = ?').get(memoryId) as { vector: Buffer; dimensions: number } | undefined;
2459
- if (!row) return null;
2460
- return new Float32Array(row.vector.buffer, row.vector.byteOffset, row.dimensions);
2461
- }
2600
+ // upsertHubMemoryEmbedding / getHubMemoryEmbedding removed:
2601
+ // hub memory vectors are now computed on-the-fly at search time.
2462
2602
 
2463
2603
  searchHubMemories(query: string, options?: { userId?: string; maxResults?: number }): Array<{ hit: HubMemorySearchRow; rank: number }> {
2464
2604
  const limit = options?.maxResults ?? 10;
@@ -2478,17 +2618,7 @@ export class SqliteStore {
2478
2618
  return rows.map((row, idx) => ({ hit: row, rank: idx + 1 }));
2479
2619
  }
2480
2620
 
2481
- getVisibleHubMemoryEmbeddings(userId: string): Array<{ memoryId: string; vector: Float32Array }> {
2482
- const rows = this.db.prepare(`
2483
- SELECT hme.memory_id, hme.vector, hme.dimensions
2484
- FROM hub_memory_embeddings hme
2485
- JOIN hub_memories hm ON hm.id = hme.memory_id
2486
- `).all() as Array<{ memory_id: string; vector: Buffer; dimensions: number }>;
2487
- return rows.map(r => ({
2488
- memoryId: r.memory_id,
2489
- vector: new Float32Array(r.vector.buffer, r.vector.byteOffset, r.dimensions),
2490
- }));
2491
- }
2621
+ // getVisibleHubMemoryEmbeddings removed: vectors computed on-the-fly at search time.
2492
2622
 
2493
2623
  getVisibleHubSearchHitByMemoryId(memoryId: string, userId: string): HubMemorySearchRow | null {
2494
2624
  const row = this.db.prepare(`
@@ -2740,6 +2870,7 @@ interface ClientHubConnection {
2740
2870
  connectedAt: number;
2741
2871
  identityKey?: string;
2742
2872
  lastKnownStatus?: string;
2873
+ hubInstanceId?: string;
2743
2874
  }
2744
2875
 
2745
2876
  interface ClientHubConnectionRow {
@@ -2751,6 +2882,7 @@ interface ClientHubConnectionRow {
2751
2882
  connected_at: number;
2752
2883
  identity_key?: string;
2753
2884
  last_known_status?: string;
2885
+ hub_instance_id?: string;
2754
2886
  }
2755
2887
 
2756
2888
  function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnection {
@@ -2763,6 +2895,7 @@ function rowToClientHubConnection(row: ClientHubConnectionRow): ClientHubConnect
2763
2895
  connectedAt: row.connected_at,
2764
2896
  identityKey: row.identity_key || "",
2765
2897
  lastKnownStatus: row.last_known_status || "",
2898
+ hubInstanceId: row.hub_instance_id || "",
2766
2899
  };
2767
2900
  }
2768
2901
 
@@ -47,7 +47,10 @@ export function createMemoryGetTool(store: SqliteStore): ToolDefinition {
47
47
  return { error: `Chunk not found: ${ref.chunkId}` };
48
48
  }
49
49
 
50
- const content = chunk.content;
50
+ let content = chunk.content;
51
+ if (content.length > maxChars) {
52
+ content = content.slice(0, maxChars) + "…";
53
+ }
51
54
 
52
55
  const result: GetResult = {
53
56
  content,
package/src/types.ts CHANGED
@@ -150,6 +150,8 @@ export type SummaryProvider =
150
150
  | "bedrock"
151
151
  | "zhipu"
152
152
  | "siliconflow"
153
+ | "deepseek"
154
+ | "moonshot"
153
155
  | "bailian"
154
156
  | "cohere"
155
157
  | "mistral"