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
@@ -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
- TAVILY_API_KEY: process.env.TAVILY_API_KEY || tavilyApiKey,
295
- BRAVE_API_KEY: process.env.BRAVE_API_KEY || braveApiKey,
296
- EXA_API_KEY: process.env.EXA_API_KEY || exaApiKey,
297
- SERPER_API_KEY: process.env.SERPER_API_KEY || serperApiKey,
298
- GITHUB_TOKEN: process.env.GITHUB_TOKEN || githubToken,
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
  });
@@ -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 = 'node';
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
- '(version 1)',
74
- '(allow default)',
75
- '(deny file-write*)',
76
- '(allow file-write*',
77
- ` (subpath "/private/tmp")`,
78
- ` (subpath "/tmp")`,
79
- ` (subpath "${process.cwd()}")`,
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, 'node', '--no-warnings', scriptPath];
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
 
@@ -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 { loadNodes, atomicWrite, safeStringify } from './vault.mjs';
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 = loadNodes(vaultDir).filter((n) => n.status === 'active');
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 = loadNodes(vaultDir)
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 = loadNodes(vaultDir).filter((n) => n.status === 'active');
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 = loadNodes(vaultDir).filter((n) => n.status === 'active');
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
  }