total-recall-brain 3.4.0 → 3.6.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.
@@ -159,5 +159,21 @@ export interface ConfigJson {
159
159
  brain?: {
160
160
  preferred_agent?: string
161
161
  }
162
+ secrets?: {
163
+ google_api_key?: string
164
+ anthropic_api_key?: string
165
+ openai_api_key?: string
166
+ tavily_api_key?: string
167
+ brave_api_key?: string
168
+ exa_api_key?: string
169
+ serper_api_key?: string
170
+ github_token?: string
171
+ }
172
+ }
173
+
174
+ export interface UpdateCheckResult {
175
+ currentVersion: string
176
+ latestVersion: string
177
+ updateAvailable: boolean
162
178
  }
163
179
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.4.0",
3
+ "version": "3.6.0",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -65,7 +65,8 @@
65
65
  "jsonwebtoken": "^9.0.3",
66
66
  "natural": "^8.1.1",
67
67
  "yaml": "^2.9.0",
68
- "zod": "^4.4.3"
68
+ "zod": "^4.4.3",
69
+ "ws": "^8.18.0"
69
70
  },
70
71
  "devDependencies": {
71
72
  "skills": "^1.5.7",
@@ -0,0 +1 @@
1
+ {"id":"0efa65c2-0231-4267-a304-e0285e50011e","topic":"Total Recall repository improvement research: SSSS validated write pipeline, filesystem-native memory engines, local-first semantic indexing, Express 5 API hardening, 2026 agent memory architecture patterns","status":"pending","priority":"high","notes":"Queued from repo-wide analysis request on 2026-06-01. Focus on current best practices and meta-patterns for filesystem-native memory OS, validated operation envelopes, append-only logs, cache invalidation, research queue architecture, and secure local-first AI tools.","node_slug":null,"research_phase":"acquisition","created_at":"2026-06-01T06:07:40.102Z","updated_at":"2026-06-01T06:07:40.126Z","completed_at":null,"summary":{"id":"0efa65c2-0231-4267-a304-e0285e50011e","topic":"Total Recall repository improvement research: SSSS validated write pipeline, filesystem-native memory engines, local-first semantic indexing, Express 5 API hardening, 2026 agent memory architecture patterns","status":"pending","phase":"acquisition","last_updated":"2026-06-01T06:07:40.126Z","conclusions":[],"ongoing_directions":["Research project is enqueued / in progress in phase: acquisition"],"sources_count":0}}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * total-recall collab
3
+ *
4
+ * Start the collaboration backend server and frontend development portal.
5
+ *
6
+ * Usage:
7
+ * npx total-recall collab
8
+ */
9
+
10
+ import { spawn } from 'node:child_process';
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
15
+ const COLLAB_DIR = path.join(__dirname, '..', '..', 'collab');
16
+
17
+ export default function collab(args) {
18
+ if (args.includes('--help') || args.includes('-h')) {
19
+ console.log(`
20
+ total-recall collab — Start collaboration workspace
21
+
22
+ Usage: total-recall collab [options]
23
+
24
+ Options:
25
+ --help Show this help
26
+
27
+ Starts the Express/WebSockets backend server and Vite frontend portal.
28
+ `);
29
+ return;
30
+ }
31
+
32
+ console.log(` ⚡ Starting Total Recall collaboration workspace...`);
33
+ console.log(` Press Ctrl+C to stop.\n`);
34
+
35
+ const child = spawn('npm', ['run', 'dev'], {
36
+ cwd: COLLAB_DIR,
37
+ stdio: 'inherit',
38
+ shell: true,
39
+ });
40
+
41
+ process.on('SIGINT', () => {
42
+ child.kill('SIGINT');
43
+ process.exit(0);
44
+ });
45
+
46
+ child.on('exit', (code) => {
47
+ process.exit(code ?? 0);
48
+ });
49
+ }
package/src/cli/help.mjs CHANGED
@@ -148,6 +148,6 @@ function printGeneralHelp() {
148
148
  \x1b[1mAll Commands Available:\x1b[0m
149
149
  \x1b[33minit\x1b[0m, \x1b[33msetup\x1b[0m, \x1b[33mstart\x1b[0m, \x1b[33mdeploy\x1b[0m, \x1b[33mdoctor\x1b[0m, \x1b[33mdream\x1b[0m, \x1b[33mlint\x1b[0m, \x1b[33mdaemon\x1b[0m, \x1b[33mbackup\x1b[0m, \x1b[33mrestore\x1b[0m, \x1b[33msync\x1b[0m, \x1b[33mstatus\x1b[0m,
150
150
  \x1b[33mgenerate-pat\x1b[0m, \x1b[33mhash-password\x1b[0m, \x1b[33mreset-password\x1b[0m, \x1b[33mcompile\x1b[0m, \x1b[33mrebuild\x1b[0m, \x1b[33msnapshot\x1b[0m, \x1b[33mmigrate\x1b[0m,
151
- \x1b[33mimport\x1b[0m, \x1b[33mingest\x1b[0m, \x1b[33mconnect\x1b[0m, \x1b[33mrelay\x1b[0m, \x1b[33muninstall\x1b[0m, \x1b[33mchat\x1b[0m, \x1b[33mremember\x1b[0m, \x1b[33mrecall\x1b[0m, \x1b[33mresearch\x1b[0m, \x1b[33mmap\x1b[0m, \x1b[33mbrain\x1b[0m, \x1b[33mskill\x1b[0m
151
+ \x1b[33mimport\x1b[0m, \x1b[33mingest\x1b[0m, \x1b[33mconnect\x1b[0m, \x1b[33mrelay\x1b[0m, \x1b[33muninstall\x1b[0m, \x1b[33mchat\x1b[0m, \x1b[33mremember\x1b[0m, \x1b[33mrecall\x1b[0m, \x1b[33mresearch\x1b[0m, \x1b[33mmap\x1b[0m, \x1b[33mbrain\x1b[0m, \x1b[33mskill\x1b[0m, \x1b[33mcollab\x1b[0m
152
152
  `);
153
153
  }
@@ -137,6 +137,47 @@ export function autoPruneStorage(brainDir, vaultDir, conflictsDir) {
137
137
  pruneDir(inboxDir, draftMaxAgeMs); // Keeps the local dev inbox fully clean of automatic research noise
138
138
  if (conflictsDir) pruneDir(conflictsDir, draftMaxAgeMs);
139
139
 
140
+ // Prune scheduler tasks queue: move completed/failed ones to archive, delete archives older than logMaxAgeMs
141
+ const tasksQueueDir = path.join(brainDir, 'scheduler', 'queue');
142
+ const tasksArchiveDir = path.join(tasksQueueDir, 'archive');
143
+ if (fs.existsSync(tasksQueueDir)) {
144
+ try {
145
+ if (!fs.existsSync(tasksArchiveDir)) {
146
+ fs.mkdirSync(tasksArchiveDir, { recursive: true });
147
+ }
148
+ const files = fs.readdirSync(tasksQueueDir);
149
+ for (const file of files) {
150
+ if (!file.endsWith('.md')) continue;
151
+ const fullPath = path.join(tasksQueueDir, file);
152
+ try {
153
+ const raw = fs.readFileSync(fullPath, 'utf8');
154
+ const { data } = matter(raw);
155
+ if (data.status === 'completed' || data.status === 'failed') {
156
+ fs.renameSync(fullPath, path.join(tasksArchiveDir, file));
157
+ }
158
+ } catch (e) {
159
+ // ignore parsing/permission errors for individual files
160
+ }
161
+ }
162
+
163
+ const archiveFiles = fs.readdirSync(tasksArchiveDir);
164
+ for (const file of archiveFiles) {
165
+ if (!file.endsWith('.md')) continue;
166
+ const fullPath = path.join(tasksArchiveDir, file);
167
+ try {
168
+ const stat = fs.statSync(fullPath);
169
+ if (now - stat.mtimeMs > logMaxAgeMs) {
170
+ fs.unlinkSync(fullPath);
171
+ }
172
+ } catch (e) {
173
+ // ignore
174
+ }
175
+ }
176
+ } catch (err) {
177
+ logger.warn('dream', `Failed to prune scheduler tasks: ${err.message}`);
178
+ }
179
+ }
180
+
140
181
  // Automatically prune transient planning files in every active/historical conversation folder,
141
182
  // while permanently preserving the conversations/threads themselves (.system_generated)
142
183
  try {
@@ -254,6 +254,27 @@ export async function callLocalRuntime(prompt, system, config) {
254
254
 
255
255
  let lastError;
256
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
+
257
278
  // Try agents in priority order with retry budget
258
279
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
259
280
  const agent = attempt === 0 ? resolveAgent(config) : resolveAgent({
@@ -289,13 +310,15 @@ export async function callLocalRuntime(prompt, system, config) {
289
310
  maxBuffer: 10 * 1024 * 1024,
290
311
  env: {
291
312
  ...process.env,
292
- GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || googleApiKey,
293
- GEMINI_API_KEY: process.env.GEMINI_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
  });