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
package/src/core/runtime.mjs
CHANGED
|
@@ -43,7 +43,6 @@ export function findBinaryInPath(binaryName) {
|
|
|
43
43
|
|
|
44
44
|
const DEFAULT_AGENTS = [
|
|
45
45
|
{ name: 'antigravity', binary: 'antigravity', flags: '--sandbox=false --yolo -o json', priority: 1, enabled: true, exec: 'flag' },
|
|
46
|
-
{ name: 'gemini', binary: 'gemini', flags: '--sandbox=false --yolo -o json', priority: 2, enabled: true, exec: 'flag' },
|
|
47
46
|
{ name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions', priority: 3, enabled: true, exec: 'flag' },
|
|
48
47
|
{ name: 'codex', binary: 'codex', flags: '--full-auto --json', priority: 4, enabled: true, exec: 'subcommand' },
|
|
49
48
|
];
|
|
@@ -255,6 +254,27 @@ export async function callLocalRuntime(prompt, system, config) {
|
|
|
255
254
|
|
|
256
255
|
let lastError;
|
|
257
256
|
|
|
257
|
+
// Load dynamic secrets from secrets.enc to ensure changes take effect immediately without server restart
|
|
258
|
+
let dynamicSecrets = {};
|
|
259
|
+
try {
|
|
260
|
+
const pathsToCheck = [
|
|
261
|
+
path.join(process.cwd(), '.agent', 'secrets.enc'),
|
|
262
|
+
path.join(agentDir, 'secrets.enc'),
|
|
263
|
+
path.join(os.homedir(), '.agent', 'skills', 'total-recall', 'config', 'secrets.enc'),
|
|
264
|
+
path.join(agentDir, 'skills', 'total-recall', 'config', 'secrets.enc'),
|
|
265
|
+
];
|
|
266
|
+
for (const p of pathsToCheck) {
|
|
267
|
+
if (fs.existsSync(p)) {
|
|
268
|
+
try {
|
|
269
|
+
dynamicSecrets = JSON.parse(fs.readFileSync(p, 'utf8') || '{}');
|
|
270
|
+
if (Object.keys(dynamicSecrets).length > 0) {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
} catch {}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
} catch {}
|
|
277
|
+
|
|
258
278
|
// Try agents in priority order with retry budget
|
|
259
279
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
260
280
|
const agent = attempt === 0 ? resolveAgent(config) : resolveAgent({
|
|
@@ -290,12 +310,15 @@ export async function callLocalRuntime(prompt, system, config) {
|
|
|
290
310
|
maxBuffer: 10 * 1024 * 1024,
|
|
291
311
|
env: {
|
|
292
312
|
...process.env,
|
|
293
|
-
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || googleApiKey,
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
313
|
+
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || dynamicSecrets.google_api_key || googleApiKey,
|
|
314
|
+
GEMINI_API_KEY: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || dynamicSecrets.google_api_key || googleApiKey,
|
|
315
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || dynamicSecrets.anthropic_api_key,
|
|
316
|
+
OPENAI_API_KEY: process.env.OPENAI_API_KEY || dynamicSecrets.openai_api_key,
|
|
317
|
+
TAVILY_API_KEY: process.env.TAVILY_API_KEY || dynamicSecrets.tavily_api_key || tavilyApiKey,
|
|
318
|
+
BRAVE_API_KEY: process.env.BRAVE_API_KEY || dynamicSecrets.brave_api_key || braveApiKey,
|
|
319
|
+
EXA_API_KEY: process.env.EXA_API_KEY || dynamicSecrets.exa_api_key || exaApiKey,
|
|
320
|
+
SERPER_API_KEY: process.env.SERPER_API_KEY || dynamicSecrets.serper_api_key || serperApiKey,
|
|
321
|
+
GITHUB_TOKEN: process.env.GITHUB_TOKEN || dynamicSecrets.github_token || githubToken,
|
|
299
322
|
},
|
|
300
323
|
cwd: process.cwd(),
|
|
301
324
|
});
|
package/src/core/sandbox.mjs
CHANGED
|
@@ -1,10 +1,52 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
1
|
+
import { spawn, spawnSync } from 'child_process';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import crypto from "node:crypto";
|
|
6
6
|
import { logger } from './logger.mjs';
|
|
7
7
|
|
|
8
|
+
let macSandboxProbe = null;
|
|
9
|
+
|
|
10
|
+
function escapeSandboxPath(value) {
|
|
11
|
+
return String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function buildMacSandboxProfile({ allowNetwork }) {
|
|
15
|
+
return [
|
|
16
|
+
'(version 1)',
|
|
17
|
+
'(allow default)',
|
|
18
|
+
'(deny file-write*)',
|
|
19
|
+
'(allow file-write*',
|
|
20
|
+
` (subpath "/private/tmp")`,
|
|
21
|
+
` (subpath "/tmp")`,
|
|
22
|
+
` (subpath "${escapeSandboxPath(process.cwd())}")`,
|
|
23
|
+
` (subpath "${escapeSandboxPath(os.tmpdir())}")`,
|
|
24
|
+
`)`,
|
|
25
|
+
allowNetwork ? '' : '(deny network-outbound)'
|
|
26
|
+
].filter(Boolean).join('\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function canUseMacSandbox(profile) {
|
|
30
|
+
if (process.platform !== 'darwin') return false;
|
|
31
|
+
if (macSandboxProbe !== null) return macSandboxProbe;
|
|
32
|
+
const result = spawnSync('sandbox-exec', [
|
|
33
|
+
'-p',
|
|
34
|
+
profile,
|
|
35
|
+
process.execPath,
|
|
36
|
+
'--no-warnings',
|
|
37
|
+
'-e',
|
|
38
|
+
'process.exit(0)'
|
|
39
|
+
], {
|
|
40
|
+
encoding: 'utf8',
|
|
41
|
+
timeout: 1500
|
|
42
|
+
});
|
|
43
|
+
macSandboxProbe = result.status === 0;
|
|
44
|
+
if (!macSandboxProbe) {
|
|
45
|
+
logger.warn('sandbox', 'macOS sandbox-exec probe failed; falling back to Node process limits.');
|
|
46
|
+
}
|
|
47
|
+
return macSandboxProbe;
|
|
48
|
+
}
|
|
49
|
+
|
|
8
50
|
/**
|
|
9
51
|
* Command Sanitizer / Whitelist Execution Validator
|
|
10
52
|
* Blocks high-risk actions (destructive deletes, reverse shells, shell pipes)
|
|
@@ -62,33 +104,21 @@ export async function runInSandbox(scriptPath, timeoutMs = 5000, options = {}) {
|
|
|
62
104
|
safeEnv.NODE_OPTIONS = '--experimental-vm-modules';
|
|
63
105
|
|
|
64
106
|
// OS-specific sandboxing wrappers
|
|
65
|
-
let cmd =
|
|
107
|
+
let cmd = process.execPath;
|
|
66
108
|
let args = ['--no-warnings', scriptPath];
|
|
67
109
|
let tempSbProfilePath = null;
|
|
68
110
|
|
|
69
111
|
try {
|
|
70
112
|
if (process.platform === 'darwin') {
|
|
71
113
|
// macOS sandbox-exec integration
|
|
72
|
-
const profile =
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
` (subpath "${os.tmpdir()}")`,
|
|
81
|
-
`)`,
|
|
82
|
-
allowNetwork
|
|
83
|
-
? ''
|
|
84
|
-
: '(deny network-outbound)'
|
|
85
|
-
].filter(Boolean).join('\n');
|
|
86
|
-
|
|
87
|
-
tempSbProfilePath = path.join(os.tmpdir(), `tr-sandbox-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.sb`);
|
|
88
|
-
fs.writeFileSync(tempSbProfilePath, profile, 'utf8');
|
|
89
|
-
|
|
90
|
-
cmd = 'sandbox-exec';
|
|
91
|
-
args = ['-f', tempSbProfilePath, 'node', '--no-warnings', scriptPath];
|
|
114
|
+
const profile = buildMacSandboxProfile({ allowNetwork });
|
|
115
|
+
if (canUseMacSandbox(profile)) {
|
|
116
|
+
tempSbProfilePath = path.join(os.tmpdir(), `tr-sandbox-${Date.now()}-${crypto.randomBytes(4).toString("hex")}.sb`);
|
|
117
|
+
fs.writeFileSync(tempSbProfilePath, profile, 'utf8');
|
|
118
|
+
|
|
119
|
+
cmd = 'sandbox-exec';
|
|
120
|
+
args = ['-f', tempSbProfilePath, process.execPath, '--no-warnings', scriptPath];
|
|
121
|
+
}
|
|
92
122
|
} else if (process.platform === 'linux') {
|
|
93
123
|
// Linux namespace isolation check via unshare
|
|
94
124
|
const hasUnshare = fs.existsSync('/usr/bin/unshare') || fs.existsSync('/bin/unshare');
|
|
@@ -98,7 +128,7 @@ export async function runInSandbox(scriptPath, timeoutMs = 5000, options = {}) {
|
|
|
98
128
|
if (!allowNetwork) {
|
|
99
129
|
unshareArgs.push('--net');
|
|
100
130
|
}
|
|
101
|
-
args = [...unshareArgs,
|
|
131
|
+
args = [...unshareArgs, process.execPath, '--no-warnings', scriptPath];
|
|
102
132
|
}
|
|
103
133
|
}
|
|
104
134
|
} catch (err) {
|
|
@@ -117,14 +147,14 @@ export async function runInSandbox(scriptPath, timeoutMs = 5000, options = {}) {
|
|
|
117
147
|
proc.stdout.on('data', (data) => { stdout += data.toString(); });
|
|
118
148
|
proc.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
119
149
|
|
|
120
|
-
proc.on('close', (code) => {
|
|
150
|
+
proc.on('close', (code, signal) => {
|
|
121
151
|
if (tempSbProfilePath && fs.existsSync(tempSbProfilePath)) {
|
|
122
152
|
try { fs.unlinkSync(tempSbProfilePath); } catch {}
|
|
123
153
|
}
|
|
124
154
|
if (code === 0) {
|
|
125
|
-
resolve({ success: true, output: stdout });
|
|
155
|
+
resolve({ success: true, output: stdout, code, signal });
|
|
126
156
|
} else {
|
|
127
|
-
resolve({ success: false, output: stderr || stdout, code });
|
|
157
|
+
resolve({ success: false, output: stderr || stdout, code, signal });
|
|
128
158
|
}
|
|
129
159
|
});
|
|
130
160
|
|
package/src/core/scheduler.mjs
CHANGED
|
@@ -2,7 +2,8 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import matter from 'gray-matter';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
|
-
import {
|
|
5
|
+
import { atomicWrite, safeStringify } from './vault.mjs';
|
|
6
|
+
import { getNodes } from './vault-cache.mjs';
|
|
6
7
|
import { logger } from './logger.mjs';
|
|
7
8
|
import { loadQueue, updateQueueItem } from './research-queue.mjs';
|
|
8
9
|
|
|
@@ -243,7 +244,7 @@ function generateProactiveResearchTask() {
|
|
|
243
244
|
* Pick a random active memory node and create a clarity review task.
|
|
244
245
|
*/
|
|
245
246
|
function generateClarityReviewTask(vaultDir) {
|
|
246
|
-
const nodes =
|
|
247
|
+
const nodes = getNodes(vaultDir).filter((n) => n.status === 'active');
|
|
247
248
|
if (nodes.length === 0) {
|
|
248
249
|
return makeFallbackTask('memory-maintenance', 'No active nodes to review');
|
|
249
250
|
}
|
|
@@ -266,7 +267,7 @@ function generateClarityReviewTask(vaultDir) {
|
|
|
266
267
|
* Pick the oldest fact node and create a staleness check task.
|
|
267
268
|
*/
|
|
268
269
|
function generateStalenessCheckTask(vaultDir) {
|
|
269
|
-
const nodes =
|
|
270
|
+
const nodes = getNodes(vaultDir)
|
|
270
271
|
.filter((n) => n.status === 'active' && n.category === 'facts')
|
|
271
272
|
.sort((a, b) => {
|
|
272
273
|
const aDate = new Date(a.last_accessed || a.updated || 0).getTime();
|
|
@@ -276,7 +277,7 @@ function generateStalenessCheckTask(vaultDir) {
|
|
|
276
277
|
|
|
277
278
|
if (nodes.length === 0) {
|
|
278
279
|
// No facts? Pick any old node
|
|
279
|
-
const allNodes =
|
|
280
|
+
const allNodes = getNodes(vaultDir).filter((n) => n.status === 'active');
|
|
280
281
|
if (allNodes.length === 0) {
|
|
281
282
|
return makeFallbackTask('research-acquisition', 'No facts to verify');
|
|
282
283
|
}
|
|
@@ -314,7 +315,7 @@ function generateStalenessCheckTask(vaultDir) {
|
|
|
314
315
|
* Pick a cluster of related nodes and create an inference task.
|
|
315
316
|
*/
|
|
316
317
|
function generateInferenceTask(vaultDir) {
|
|
317
|
-
const nodes =
|
|
318
|
+
const nodes = getNodes(vaultDir).filter((n) => n.status === 'active');
|
|
318
319
|
if (nodes.length < 3) {
|
|
319
320
|
return makeFallbackTask('system2-deliberation', 'Not enough nodes for inference');
|
|
320
321
|
}
|