total-recall-brain 3.3.0 → 3.5.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 (62) hide show
  1. package/README.md +1 -1
  2. package/bin/antigravity.mjs +19 -8
  3. package/extension/background.js +13 -1
  4. package/extension/content-script.js +18 -1
  5. package/extension/lib/brain-client.js +18 -5
  6. package/extension/options/options.js +9 -4
  7. package/frontend/index.html +8 -0
  8. package/frontend/src/api.ts +20 -1
  9. package/frontend/src/pages/ChatPage.tsx +9 -6
  10. package/frontend/src/pages/HealthPage.tsx +179 -7
  11. package/frontend/src/pages/IntegrationsPage.tsx +4 -2
  12. package/frontend/src/pages/SettingsPage.tsx +197 -2
  13. package/frontend/src/types.ts +16 -0
  14. package/package.json +2 -2
  15. package/scaffold/.agent/skills/total-recall/SKILL.md +55 -6
  16. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -2
  17. package/scaffold/.agent/skills/total-recall/research-queue.jsonl +1 -0
  18. package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -6
  19. package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +19 -0
  20. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +33 -5
  21. package/src/cli/forget.mjs +18 -12
  22. package/src/cli/ingest/index.mjs +3 -2
  23. package/src/cli/map.mjs +2 -2
  24. package/src/cli/rebuild.mjs +1 -1
  25. package/src/cli/remember.mjs +21 -11
  26. package/src/cli/share.mjs +7 -2
  27. package/src/core/append-log.mjs +131 -0
  28. package/src/core/clarity-rewriter.mjs +10 -6
  29. package/src/core/conclusion-writer.mjs +3 -2
  30. package/src/core/config.mjs +6 -0
  31. package/src/core/conflict-detector.mjs +4 -2
  32. package/src/core/context-compiler.mjs +412 -0
  33. package/src/core/daemon-loop.mjs +15 -0
  34. package/src/core/dream.mjs +4 -4
  35. package/src/core/drift-detector.mjs +3 -3
  36. package/src/core/embeddings.mjs +60 -10
  37. package/src/core/fact-seeker.mjs +70 -44
  38. package/src/core/import-rules.mjs +3 -2
  39. package/src/core/inference-engine.mjs +5 -4
  40. package/src/core/migrate.mjs +4 -3
  41. package/src/core/optimizer.mjs +6 -5
  42. package/src/core/parallel-context.mjs +288 -0
  43. package/src/core/post-mortem.mjs +4 -3
  44. package/src/core/research-queue.mjs +4 -3
  45. package/src/core/runtime.mjs +30 -7
  46. package/src/core/sandbox.mjs +56 -26
  47. package/src/core/scheduler.mjs +6 -5
  48. package/src/core/schema.mjs +483 -2
  49. package/src/core/search.mjs +167 -101
  50. package/src/core/surface.mjs +203 -53
  51. package/src/core/validated-write.mjs +100 -0
  52. package/src/core/vault-cache.mjs +46 -18
  53. package/src/core/vault.mjs +17 -29
  54. package/src/core/vector-field.mjs +659 -0
  55. package/src/core/vector-store.mjs +85 -0
  56. package/src/server/api.mjs +4 -3
  57. package/src/server/index.mjs +13 -61
  58. package/src/server/rest.mjs +341 -63
  59. package/src/server/route-manifest.json +352 -77
  60. package/src/server/routes/memory.mjs +85 -39
  61. package/src/server/routes/sessions.mjs +6 -2
  62. package/src/server/routes/share.mjs +9 -2
@@ -0,0 +1,85 @@
1
+ /**
2
+ * src/core/vector-store.mjs
3
+ *
4
+ * Pluggable vector index interface. Defaults to in-memory cosine similarity scanning,
5
+ * but structured to allow drop-in SQLite-VSS or HNSWLib index engines.
6
+ */
7
+
8
+ export class VectorStore {
9
+ constructor() {
10
+ this.vectors = new Map(); // slug -> { embedding: number[], metadata?: any }
11
+ }
12
+
13
+ /**
14
+ * Load vector index from standard index payload.
15
+ * @param {Record<string, { embedding: number[], chunks?: Array<{ embedding: number[] }> }>} indexData
16
+ */
17
+ load(indexData) {
18
+ this.vectors.clear();
19
+ if (!indexData) return;
20
+ for (const [slug, entry] of Object.entries(indexData)) {
21
+ if (entry && Array.isArray(entry.embedding)) {
22
+ this.vectors.set(slug, {
23
+ embedding: entry.embedding,
24
+ chunks: entry.chunks || []
25
+ });
26
+ }
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Add or update a single vector.
32
+ */
33
+ add(slug, embedding, chunks = []) {
34
+ if (Array.isArray(embedding)) {
35
+ this.vectors.set(slug, { embedding, chunks });
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Delete a single vector.
41
+ */
42
+ delete(slug) {
43
+ this.vectors.delete(slug);
44
+ }
45
+
46
+ /**
47
+ * Perform cosine-similarity based k-nearest neighbors (k-NN) search.
48
+ * Compares query against parent embedding and all child chunk embeddings,
49
+ * matching on the maximum similarity.
50
+ *
51
+ * @param {number[]} queryEmbedding - The query vector
52
+ * @param {number} topK - Maximum results
53
+ * @param {Set<string>|null} filterSet - Optional allow-list of slugs
54
+ * @param {Function} cosineSimilarityFn - Cosine similarity function
55
+ * @returns {Array<{ slug: string, similarity: number }>} Sorted matches
56
+ */
57
+ search(queryEmbedding, topK = 5, filterSet = null, cosineSimilarityFn) {
58
+ if (!queryEmbedding || this.vectors.size === 0) return [];
59
+
60
+ const results = [];
61
+ for (const [slug, data] of this.vectors.entries()) {
62
+ if (filterSet && !filterSet.has(slug)) continue;
63
+
64
+ let maxSimilarity = 0;
65
+ if (Array.isArray(data.embedding)) {
66
+ maxSimilarity = cosineSimilarityFn(queryEmbedding, data.embedding);
67
+ }
68
+
69
+ for (const chunk of data.chunks || []) {
70
+ if (Array.isArray(chunk.embedding)) {
71
+ const sim = cosineSimilarityFn(queryEmbedding, chunk.embedding);
72
+ if (sim > maxSimilarity) {
73
+ maxSimilarity = sim;
74
+ }
75
+ }
76
+ }
77
+
78
+ results.push({ slug, similarity: maxSimilarity });
79
+ }
80
+
81
+ return results
82
+ .sort((a, b) => b.similarity - a.similarity)
83
+ .slice(0, topK);
84
+ }
85
+ }
@@ -8,7 +8,8 @@ import { requireAuth, requireScope, loginHandler, logoutHandler, changePasswordH
8
8
  import { logger } from '../core/logger.mjs';
9
9
  import { synthesize as synthesizeTts, isTtsEnabled, TtsNotConfiguredError } from '../core/tts.mjs';
10
10
  import { KNOWN_SCOPES, loadKeys, issueKey, revokeKey } from './keys.mjs';
11
- import { loadNodes, writeNode } from '../core/vault.mjs';
11
+ import { writeNode } from '../core/vault.mjs';
12
+ import { getNodes } from '../core/vault-cache.mjs';
12
13
  import { compileSurface } from '../core/surface.mjs';
13
14
  import { runInSandbox } from '../core/sandbox.mjs';
14
15
  import { resolveAgentDir } from '../cli/agent-dir.mjs';
@@ -599,7 +600,7 @@ ${interviewTask}`;
599
600
  if (Array.isArray(groundingNodes) && groundingNodes.length > 0) {
600
601
  try {
601
602
  const groundingVaultDir = resolveVaultDir(brainId);
602
- const allNodes = loadNodes(groundingVaultDir);
603
+ const allNodes = getNodes(groundingVaultDir);
603
604
  let groundingPrompt = '\n\n=== ACTIVE GROUNDING BRAIN NODES ===\nThe user has explicitly selected the following brain memory nodes as context for this conversation. Integrate their contents into your knowledge base and refer to them to inform your answers:';
604
605
  let groundedAny = false;
605
606
  for (const slug of groundingNodes) {
@@ -864,7 +865,7 @@ apiRouter.delete('/v1/chat/threads/:id', requireAuth, requireScope('chat:write')
864
865
  apiRouter.get('/v1/chat/suggestions', requireAuth, requireScope('chat:read'), (req, res) => {
865
866
  try {
866
867
  const suggestionsVaultDir = resolveVaultDir(req.query?.brain);
867
- const allNodes = loadNodes(suggestionsVaultDir);
868
+ const allNodes = getNodes(suggestionsVaultDir);
868
869
  // Find active or draft memory nodes that represent facts, concepts, or research
869
870
  const candidates = allNodes.filter(n => n.status !== 'archived' && n.category !== 'preferences');
870
871
 
@@ -17,6 +17,7 @@ import cookieParser from 'cookie-parser';
17
17
  import path from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import zlib from 'node:zlib';
20
+ import fs from 'node:fs';
20
21
  import {
21
22
  apiRateLimiter,
22
23
  corsOptions,
@@ -34,6 +35,13 @@ import { getDaemonStatus, ensureDaemonRunning } from '../core/daemon-control.mjs
34
35
 
35
36
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
37
  const ROOT = path.join(__dirname, '..', '..');
38
+ const PACKAGE_VERSION = (() => {
39
+ try {
40
+ return JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version || 'unknown';
41
+ } catch {
42
+ return 'unknown';
43
+ }
44
+ })();
37
45
 
38
46
  // ─── Watchdog ───────────────────────────────────────────────────────────────────
39
47
  // Attach circuit-breaker log monitor before any subsystem can emit events.
@@ -91,8 +99,6 @@ app.use(express.json({ limit: '50mb' }));
91
99
  app.use(express.urlencoded({ limit: '50mb', extended: true }));
92
100
  app.use(cookieParser());
93
101
 
94
- import fs from 'node:fs';
95
-
96
102
  // ─── Health Check ───────────────────────────────────────────────────────────────
97
103
 
98
104
  app.get('/health', requireAuthOrLocal, async (req, res) => {
@@ -134,7 +140,7 @@ app.get('/health', requireAuthOrLocal, async (req, res) => {
134
140
 
135
141
  res.json({
136
142
  status: hasCriticalIssue ? 'degraded' : 'healthy',
137
- version: '3.0.0',
143
+ version: PACKAGE_VERSION,
138
144
  uptime_seconds: Math.floor(process.uptime()),
139
145
  timestamp: new Date().toISOString(),
140
146
  disk,
@@ -205,60 +211,7 @@ try {
205
211
  logger.info('server', 'API routes mounted at /v1/chat/completions');
206
212
  }
207
213
  } catch (err) {
208
- // api.mjs may still be in standalone mode — mount it directly
209
- logger.warn('server', 'API router not exported as middleware, loading standalone routes...');
210
- const { callLocalRuntimeRaw, loadRuntimeConfig } = await import('../core/runtime.mjs');
211
- const os = await import('node:os');
212
-
213
- app.post('/v1/chat/completions', async (req, res) => {
214
- try {
215
- const authHeader = req.headers.authorization;
216
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
217
- return res.status(401).json({ error: 'Missing or invalid Authorization header' });
218
- }
219
-
220
- const configPath = path.join(configBrainDir, 'config', 'runtime.yml');
221
- const config = loadRuntimeConfig(configPath);
222
- const { messages, model, temperature } = req.body;
223
-
224
- if (!messages || messages.length === 0) {
225
- return res.status(400).json({ error: 'Messages array is required' });
226
- }
227
-
228
- const activeConfig = {
229
- ...config,
230
- model: model || config.model,
231
- temperature: temperature || config.temperature
232
- };
233
-
234
- if (model && activeConfig.agents) {
235
- const targetAgentName = model.toLowerCase();
236
- const idx = activeConfig.agents.findIndex(a => a.name === targetAgentName);
237
- if (idx >= 0) {
238
- activeConfig.agents = activeConfig.agents.map(a => ({ ...a }));
239
- activeConfig.agents[idx].priority = 0;
240
- activeConfig.agents.sort((a, b) => a.priority - b.priority);
241
- }
242
- }
243
-
244
- const response = await callLocalRuntimeRaw(messages, activeConfig);
245
-
246
- res.json({
247
- id: `chatcmpl-${Date.now()}`,
248
- object: 'chat.completion',
249
- created: Math.floor(Date.now() / 1000),
250
- model: model || config.model,
251
- choices: [{
252
- index: 0,
253
- message: response,
254
- finish_reason: 'stop'
255
- }]
256
- });
257
- } catch (error) {
258
- logger.error('api', 'API Error', { error: error.message });
259
- res.status(500).json({ error: error.message });
260
- }
261
- });
214
+ logger.error('server', `API router failed to load; /v1/chat/completions left unmounted: ${err.message}`);
262
215
  }
263
216
 
264
217
 
@@ -271,7 +224,7 @@ const frontendDist = path.join(ROOT, 'frontend', 'dist');
271
224
  if (!fs.existsSync(path.join(frontendDist, 'index.html'))) {
272
225
  const frontendDir = path.join(ROOT, 'frontend');
273
226
  if (fs.existsSync(path.join(frontendDir, 'package.json'))) {
274
- log.info('Frontend not built. Building automatically...');
227
+ logger.info('server', 'Frontend not built. Building automatically...');
275
228
  try {
276
229
  const { execSync } = await import('node:child_process');
277
230
  execSync('npm install --no-audit --no-fund 2>/dev/null && npm run build', {
@@ -279,9 +232,9 @@ if (!fs.existsSync(path.join(frontendDist, 'index.html'))) {
279
232
  stdio: 'pipe',
280
233
  timeout: 120_000,
281
234
  });
282
- log.info('Frontend build complete.');
235
+ logger.info('server', 'Frontend build complete.');
283
236
  } catch (err) {
284
- log.warn(`Frontend auto-build failed: ${err.message?.split('\\n')[0] || 'unknown error'}. Dashboard will show API info instead.`);
237
+ logger.warn('server', `Frontend auto-build failed: ${err.message?.split('\\n')[0] || 'unknown error'}. Dashboard will show API info instead.`);
285
238
  }
286
239
  }
287
240
  }
@@ -865,4 +818,3 @@ async function handleShutdown(signal) {
865
818
 
866
819
  process.on('SIGTERM', () => handleShutdown('SIGTERM'));
867
820
  process.on('SIGINT', () => handleShutdown('SIGINT'));
868
-