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.
- package/README.md +1 -1
- package/bin/antigravity.mjs +19 -8
- package/extension/background.js +13 -1
- package/extension/content-script.js +18 -1
- package/extension/lib/brain-client.js +18 -5
- package/extension/options/options.js +9 -4
- package/frontend/index.html +8 -0
- package/frontend/src/api.ts +20 -1
- package/frontend/src/pages/ChatPage.tsx +9 -6
- package/frontend/src/pages/HealthPage.tsx +179 -7
- package/frontend/src/pages/IntegrationsPage.tsx +4 -2
- package/frontend/src/pages/SettingsPage.tsx +197 -2
- package/frontend/src/types.ts +16 -0
- package/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/SKILL.md +55 -6
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -2
- package/scaffold/.agent/skills/total-recall/research-queue.jsonl +1 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -6
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +19 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +33 -5
- package/src/cli/forget.mjs +18 -12
- package/src/cli/ingest/index.mjs +3 -2
- package/src/cli/map.mjs +2 -2
- package/src/cli/rebuild.mjs +1 -1
- package/src/cli/remember.mjs +21 -11
- package/src/cli/share.mjs +7 -2
- package/src/core/append-log.mjs +131 -0
- package/src/core/clarity-rewriter.mjs +10 -6
- package/src/core/conclusion-writer.mjs +3 -2
- package/src/core/config.mjs +6 -0
- package/src/core/conflict-detector.mjs +4 -2
- package/src/core/context-compiler.mjs +412 -0
- package/src/core/daemon-loop.mjs +15 -0
- package/src/core/dream.mjs +4 -4
- package/src/core/drift-detector.mjs +3 -3
- package/src/core/embeddings.mjs +60 -10
- package/src/core/fact-seeker.mjs +70 -44
- package/src/core/import-rules.mjs +3 -2
- package/src/core/inference-engine.mjs +5 -4
- package/src/core/migrate.mjs +4 -3
- package/src/core/optimizer.mjs +6 -5
- package/src/core/parallel-context.mjs +288 -0
- package/src/core/post-mortem.mjs +4 -3
- package/src/core/research-queue.mjs +4 -3
- package/src/core/runtime.mjs +30 -7
- package/src/core/sandbox.mjs +56 -26
- package/src/core/scheduler.mjs +6 -5
- package/src/core/schema.mjs +483 -2
- package/src/core/search.mjs +167 -101
- package/src/core/surface.mjs +203 -53
- package/src/core/validated-write.mjs +100 -0
- package/src/core/vault-cache.mjs +46 -18
- package/src/core/vault.mjs +17 -29
- package/src/core/vector-field.mjs +659 -0
- package/src/core/vector-store.mjs +85 -0
- package/src/server/api.mjs +4 -3
- package/src/server/index.mjs +13 -61
- package/src/server/rest.mjs +341 -63
- package/src/server/route-manifest.json +352 -77
- package/src/server/routes/memory.mjs +85 -39
- package/src/server/routes/sessions.mjs +6 -2
- 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
|
+
}
|
package/src/server/api.mjs
CHANGED
|
@@ -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 {
|
|
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 =
|
|
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 =
|
|
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
|
|
package/src/server/index.mjs
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
235
|
+
logger.info('server', 'Frontend build complete.');
|
|
283
236
|
} catch (err) {
|
|
284
|
-
|
|
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
|
-
|