total-recall-brain 3.13.0 → 3.13.2

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 (152) hide show
  1. package/README.md +91 -108
  2. package/bin/antigravity.mjs +81 -52
  3. package/bin/total-recall.mjs +32 -57
  4. package/extension/background.js +1 -1
  5. package/extension/options/options.js +9 -3
  6. package/extension/sidepanel/sidepanel.js +0 -2
  7. package/frontend/index.html +4 -3
  8. package/frontend/package.json +1 -0
  9. package/frontend/src/App.tsx +109 -152
  10. package/frontend/src/api.ts +605 -23
  11. package/frontend/src/components/BrainSelector.tsx +211 -199
  12. package/frontend/src/components/DocumentEditorModal.tsx +57 -0
  13. package/frontend/src/components/DocumentTable.tsx +47 -0
  14. package/frontend/src/components/Graph3D.tsx +120 -71
  15. package/frontend/src/components/UsageChart.tsx +3 -3
  16. package/frontend/src/components/brand/BrandMark.tsx +95 -0
  17. package/frontend/src/index.css +286 -86
  18. package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
  19. package/frontend/src/pages/ChatPage.tsx +103 -45
  20. package/frontend/src/pages/DesignDocsPage.tsx +5 -5
  21. package/frontend/src/pages/FilesPage.tsx +10 -6
  22. package/frontend/src/pages/GraphPage.tsx +5 -3
  23. package/frontend/src/pages/HelpPage.tsx +2 -2
  24. package/frontend/src/pages/InboxPage.tsx +82 -0
  25. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  26. package/frontend/src/pages/IntegrationsPage.tsx +21 -10
  27. package/frontend/src/pages/LoginPage.tsx +95 -77
  28. package/frontend/src/pages/MemoryPage.tsx +31 -8
  29. package/frontend/src/pages/OnboardingPage.tsx +690 -0
  30. package/frontend/src/pages/OpenWikiPage.tsx +412 -323
  31. package/frontend/src/pages/SettingsPage.tsx +825 -922
  32. package/frontend/src/pages/SkillsPage.tsx +272 -147
  33. package/frontend/src/pages/UsagePage.tsx +23 -31
  34. package/frontend/src/pages/VaultPage.tsx +223 -0
  35. package/frontend/src/types.ts +13 -0
  36. package/package.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
  38. package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
  39. package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
  40. package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
  41. package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
  42. package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
  43. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
  44. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
  46. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
  47. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
  48. package/src/cli/brain.mjs +63 -5
  49. package/src/cli/connect.mjs +11 -19
  50. package/src/cli/daemon.mjs +5 -4
  51. package/src/cli/deploy-ui.mjs +8 -3
  52. package/src/cli/deploy.mjs +84 -27
  53. package/src/cli/ingest/index.mjs +1 -1
  54. package/src/cli/init.mjs +63 -34
  55. package/src/cli/remember.mjs +3 -3
  56. package/src/cli/secret.mjs +812 -0
  57. package/src/cli/setup.mjs +34 -7
  58. package/src/cli/share.mjs +2 -2
  59. package/src/cli/skill.mjs +396 -35
  60. package/src/cli/sync.mjs +4 -4
  61. package/src/cli/task.mjs +287 -0
  62. package/src/core/clarity-rewriter.mjs +5 -5
  63. package/src/core/config.mjs +44 -2
  64. package/src/core/conflict-detector.mjs +5 -5
  65. package/src/core/crons.mjs +54 -0
  66. package/src/core/daemon-loop.mjs +87 -235
  67. package/src/core/dream.mjs +170 -10
  68. package/src/core/env-import.mjs +456 -0
  69. package/src/core/fact-seeker.mjs +14 -14
  70. package/src/core/import-rules.mjs +20 -7
  71. package/src/core/inference-engine.mjs +3 -3
  72. package/src/core/migrate.mjs +14 -8
  73. package/src/core/okf-adapter.mjs +2 -2
  74. package/src/core/operation-validator.mjs +39 -331
  75. package/src/core/optimizer.mjs +7 -0
  76. package/src/core/post-mortem.mjs +2 -2
  77. package/src/core/project-brain.mjs +349 -0
  78. package/src/core/provider-catalog.mjs +296 -0
  79. package/src/core/quick-capture.mjs +1 -1
  80. package/src/core/remote-vault-sync.mjs +194 -0
  81. package/src/core/research-queue.mjs +7 -11
  82. package/src/core/runtime.mjs +33 -9
  83. package/src/core/scheduler.mjs +59 -5
  84. package/src/core/schema.mjs +14 -0
  85. package/src/core/search.mjs +1 -1
  86. package/src/core/secrets-env-export.mjs +323 -0
  87. package/src/core/secrets-rotate.mjs +245 -0
  88. package/src/core/secrets-store.mjs +645 -0
  89. package/src/core/semantic-index.mjs +3 -3
  90. package/src/core/skills-registry.mjs +1062 -0
  91. package/src/core/source-adapters.mjs +4 -1
  92. package/src/core/ssss-host-extension.mjs +94 -0
  93. package/src/core/ssss-kernel-bridge.mjs +530 -0
  94. package/src/core/surface.mjs +14 -0
  95. package/src/core/task-envelope.mjs +298 -0
  96. package/src/core/task-executors.mjs +600 -0
  97. package/src/core/validated-write.mjs +124 -56
  98. package/src/core/vault.mjs +21 -34
  99. package/src/core/webauthn-store.mjs +272 -0
  100. package/src/server/api.mjs +26 -16
  101. package/src/server/auth.mjs +51 -0
  102. package/src/server/index.mjs +3 -3
  103. package/src/server/keys.mjs +30 -4
  104. package/src/server/rest.mjs +198 -25
  105. package/src/server/route-manifest.json +156 -0
  106. package/src/server/routes/_shared.mjs +40 -1
  107. package/src/server/routes/docs.mjs +260 -0
  108. package/src/server/routes/memory.mjs +145 -32
  109. package/src/server/routes/research.mjs +30 -0
  110. package/src/server/routes/secrets.mjs +470 -0
  111. package/src/server/routes/share.mjs +2 -2
  112. package/src/server/routes/skills.mjs +452 -54
  113. package/src/server/routes/sync.mjs +48 -0
  114. package/src/server/routes/webauthn.mjs +184 -0
  115. package/src/server/update-route-manifest.mjs +39 -0
  116. package/templates/onboarding-interview.md +32 -22
  117. package/templates/openwiki/README.md +10 -0
  118. package/templates/openwiki/architecture.md +5 -0
  119. package/templates/openwiki/ide.md +10 -0
  120. package/templates/openwiki/memory.md +11 -0
  121. package/templates/openwiki/secrets.md +7 -0
  122. package/templates/openwiki/skills.md +7 -0
  123. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
  124. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
  125. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
  126. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
  127. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
  128. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
  129. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
  130. package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
  131. package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
  132. package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
  133. package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
  134. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
  135. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
  136. package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
  137. package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
  138. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
  139. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
  140. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
  141. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
  142. package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
  143. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
  144. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
  145. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
  146. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
  147. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
  148. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
  149. /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
  150. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
  151. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
  152. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Optional remote vault content sync (generic).
3
+ * All endpoints/paths come from config / env — no product or host-app assumptions.
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ import { spawnSync } from 'child_process';
9
+ import http from 'http';
10
+ import https from 'https';
11
+ import { createHash } from 'crypto';
12
+ import { createEngine } from '@ssss/cli/engine';
13
+ import { remoteVaultSync } from './config.mjs';
14
+ import { logger } from './logger.mjs';
15
+
16
+ function fetchExport(url, token, outStream) {
17
+ return new Promise((resolve, reject) => {
18
+ const client = url.startsWith('https') ? https : http;
19
+ const req = client.get(url, { headers: { Authorization: `Bearer ${token}` } }, (res) => {
20
+ if (res.statusCode !== 200) {
21
+ let errStr = '';
22
+ res.on('data', (c) => (errStr += String(c)));
23
+ res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${errStr}`)));
24
+ return;
25
+ }
26
+ res.pipe(outStream);
27
+ outStream.on('finish', () => resolve(true));
28
+ outStream.on('error', reject);
29
+ });
30
+ req.on('error', reject);
31
+ });
32
+ }
33
+
34
+ function pruneAssets(assetsDir, keepCount) {
35
+ if (!fs.existsSync(assetsDir)) return;
36
+ const files = fs
37
+ .readdirSync(assetsDir)
38
+ .filter((f) => f.endsWith('.tar.gz'))
39
+ .map((f) => ({ name: f, time: fs.statSync(path.join(assetsDir, f)).mtimeMs }))
40
+ .sort((a, b) => b.time - a.time);
41
+
42
+ for (let i = keepCount; i < files.length; i++) {
43
+ try {
44
+ fs.unlinkSync(path.join(assetsDir, files[i].name));
45
+ } catch {
46
+ // ignore
47
+ }
48
+ }
49
+ }
50
+
51
+ function remoteRegistryDir() {
52
+ if (process.env.TR_REMOTE_REGISTRY_DIR) return process.env.TR_REMOTE_REGISTRY_DIR;
53
+ if (remoteVaultSync.registryDir) return remoteVaultSync.registryDir;
54
+ throw new Error(
55
+ 'TR_REMOTE_REGISTRY_DIR is required for remote vault content sync (SSSS registry path for imports).',
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Replay bundle documents through the SSSS engine with an explicit registry.
61
+ */
62
+ export function importRemoteBundle(bundle, vaultDir, registryDir) {
63
+ const files = Array.isArray(bundle?.files) ? bundle.files : [];
64
+ if (files.length === 0) {
65
+ return { imported: 0 };
66
+ }
67
+ if (!registryDir) {
68
+ throw new Error('TR_REMOTE_REGISTRY_DIR is required when the bundle contains files to import.');
69
+ }
70
+ const engine = createEngine({ registryDir });
71
+ const failures = [];
72
+ for (const file of files) {
73
+ const result = engine.processOperation(
74
+ {
75
+ type: 'operation',
76
+ idempotency_key: createHash('sha256')
77
+ .update(file.path)
78
+ .update('\n')
79
+ .update(file.content)
80
+ .digest('hex'),
81
+ workspace_id: process.env.TR_REMOTE_WORKSPACE_ID || 'remote',
82
+ path: file.path,
83
+ content: file.content,
84
+ actor: { role: 'system' },
85
+ },
86
+ vaultDir,
87
+ );
88
+ if (!result.success) {
89
+ failures.push(
90
+ `${file.path}: ${(result.validation?.errors || ['unknown operation failure']).join('; ')}`,
91
+ );
92
+ }
93
+ }
94
+ if (failures.length) throw new Error(`Bundle import failed: ${failures.join(' | ')}`);
95
+ return { imported: files.length };
96
+ }
97
+
98
+ /** @deprecated alias */
99
+ export const importPortfolioBundle = importRemoteBundle;
100
+
101
+ export async function runSync() {
102
+ if (!remoteVaultSync.enabled && process.env.TR_REMOTE_VAULT_SYNC !== '1') return;
103
+
104
+ if (!remoteVaultSync.baseUrl) {
105
+ logger.error({
106
+ subsystem: 'remote-vault-sync',
107
+ message: 'TR_REMOTE_VAULT_URL / remoteVaultSync.baseUrl is not configured — skipping',
108
+ });
109
+ return;
110
+ }
111
+
112
+ const tenantDir = path.dirname(remoteVaultSync.vaultDir);
113
+ const statusFile = path.join(tenantDir, 'sync-status.json');
114
+
115
+ if (!fs.existsSync(tenantDir)) {
116
+ fs.mkdirSync(tenantDir, { recursive: true });
117
+ }
118
+
119
+ const token = process.env[remoteVaultSync.tokenRef];
120
+ if (!token) {
121
+ const errStr = `Missing ${remoteVaultSync.tokenRef} environment variable`;
122
+ logger.error({ subsystem: 'remote-vault-sync', message: errStr });
123
+ fs.writeFileSync(
124
+ statusFile,
125
+ JSON.stringify({ lastRunAt: new Date().toISOString(), ok: false, error: errStr }, null, 2),
126
+ );
127
+ return;
128
+ }
129
+
130
+ try {
131
+ const bundleUrl = `${remoteVaultSync.baseUrl.replace(/\/+$/, '')}/api/admin/export-bundle`;
132
+ const tmpBundle = path.join(tenantDir, `tmp-bundle-${process.pid}-${Date.now()}.json`);
133
+ const bundleStream = fs.createWriteStream(tmpBundle);
134
+ await fetchExport(bundleUrl, token, bundleStream);
135
+
136
+ const registryDir = remoteRegistryDir();
137
+ const ssssCmd = path.join(process.cwd(), 'node_modules', '.bin', 'ssss');
138
+ let validateCmd = 'ssss';
139
+ if (fs.existsSync(ssssCmd)) {
140
+ validateCmd = ssssCmd;
141
+ }
142
+ const valRes = spawnSync(validateCmd, ['validate', tmpBundle, '--registry', registryDir], {
143
+ encoding: 'utf8',
144
+ });
145
+ if (valRes.status !== 0) {
146
+ throw new Error(`Bundle validation failed: ${valRes.stderr}`);
147
+ }
148
+
149
+ const bundleContent = JSON.parse(fs.readFileSync(tmpBundle, 'utf8'));
150
+ const nodeCounts =
151
+ bundleContent.manifest?.primitive_inventory || bundleContent.primitive_inventory || {};
152
+
153
+ if (!fs.existsSync(remoteVaultSync.vaultDir)) {
154
+ fs.mkdirSync(remoteVaultSync.vaultDir, { recursive: true });
155
+ }
156
+ importRemoteBundle(bundleContent, remoteVaultSync.vaultDir, registryDir);
157
+
158
+ const assetsUrl = `${remoteVaultSync.baseUrl.replace(/\/+$/, '')}/api/admin/export-assets`;
159
+ if (!fs.existsSync(remoteVaultSync.assetsDir)) {
160
+ fs.mkdirSync(remoteVaultSync.assetsDir, { recursive: true });
161
+ }
162
+ const assetFile = path.join(
163
+ remoteVaultSync.assetsDir,
164
+ `assets-${new Date().toISOString().replace(/[:.]/g, '-')}.tar.gz`,
165
+ );
166
+ const assetStream = fs.createWriteStream(assetFile);
167
+ await fetchExport(assetsUrl, token, assetStream);
168
+
169
+ pruneAssets(remoteVaultSync.assetsDir, remoteVaultSync.keepAssets);
170
+
171
+ fs.unlinkSync(tmpBundle);
172
+
173
+ fs.writeFileSync(
174
+ statusFile,
175
+ JSON.stringify({
176
+ lastRunAt: new Date().toISOString(),
177
+ ok: true,
178
+ nodeCounts,
179
+ }, null, 2),
180
+ );
181
+
182
+ logger.info({ subsystem: 'remote-vault-sync', message: 'Remote vault sync completed successfully' });
183
+ } catch (err) {
184
+ logger.error({ subsystem: 'remote-vault-sync', message: `Sync failed: ${err.message}` });
185
+ try {
186
+ fs.writeFileSync(
187
+ statusFile,
188
+ JSON.stringify({ lastRunAt: new Date().toISOString(), ok: false, error: err.message }, null, 2),
189
+ );
190
+ } catch {
191
+ // ignore
192
+ }
193
+ }
194
+ }
@@ -112,7 +112,7 @@ export function compileResearchProjectSummary(item, node) {
112
112
  * Sync a research project summary node to the SSSS memory vault.
113
113
  * Creates/updates a .md node in memory-vault/facts/ named research-project-<id>.md.
114
114
  */
115
- export function syncResearchProjectNode(item) {
115
+ export async function syncResearchProjectNode(item) {
116
116
  if (!item) return;
117
117
  const vaultDir = path.join(getBrainDir(), 'memory-vault');
118
118
 
@@ -210,7 +210,7 @@ export function syncResearchProjectNode(item) {
210
210
  };
211
211
 
212
212
  try {
213
- writeNode(node, vaultDir);
213
+ await writeNode(node, vaultDir);
214
214
  } catch (e) {
215
215
  // Log or handle error gracefully
216
216
  }
@@ -257,7 +257,7 @@ export function loadQueue(overrideBrainDir) {
257
257
  const summaryPath = path.join(vaultDir, 'facts', `research-project-${item.id}.md`);
258
258
  if (!fs.existsSync(summaryPath)) {
259
259
  try {
260
- syncResearchProjectNode(item);
260
+ void syncResearchProjectNode(item);
261
261
  } catch {}
262
262
  }
263
263
  } else {
@@ -349,10 +349,8 @@ export function addToQueue({ topic, priority = 'medium', notes, brainDir: overri
349
349
  items.unshift(item);
350
350
  saveQueue(items, overrideBrainDir);
351
351
 
352
- // Sync initial pending project summary node to the vault
353
- try {
354
- syncResearchProjectNode(item);
355
- } catch {}
352
+ // Sync initial pending project summary node to the vault (async, non-blocking)
353
+ void syncResearchProjectNode(item).catch(() => {});
356
354
 
357
355
  return item;
358
356
  }
@@ -412,10 +410,8 @@ export function updateQueueItem(id, patch = {}, overrideBrainDir) {
412
410
  items[idx] = item;
413
411
  saveQueue(items, overrideBrainDir);
414
412
 
415
- // Sync updated research project summary node to the vault
416
- try {
417
- syncResearchProjectNode(item);
418
- } catch {}
413
+ // Sync updated research project summary node to the vault (async, non-blocking)
414
+ void syncResearchProjectNode(item).catch(() => {});
419
415
 
420
416
  return item;
421
417
  }
@@ -14,7 +14,13 @@ import { validateCommand } from './sandbox.mjs';
14
14
  */
15
15
  export function findBinaryInPath(binaryName) {
16
16
  const pathEnv = process.env.PATH || '';
17
- const dirs = pathEnv.split(path.delimiter);
17
+ const extra = [
18
+ path.join(os.homedir(), '.local', 'bin'),
19
+ path.join(os.homedir(), '.grok', 'bin'),
20
+ '/opt/homebrew/bin',
21
+ '/usr/local/bin',
22
+ ];
23
+ const dirs = [...new Set([...pathEnv.split(path.delimiter), ...extra].filter(Boolean))];
18
24
  for (const dir of dirs) {
19
25
  const fullPath = path.join(dir, binaryName);
20
26
  try {
@@ -35,14 +41,15 @@ export function findBinaryInPath(binaryName) {
35
41
  * Total Recall Runtime — CLI Agent Dispatch
36
42
  *
37
43
  * All reasoning tasks are dispatched to headless CLI agents.
38
- * Agent registry lives in the tr-cli-agents skill:
39
- * .agent/skills/total-recall/skills/tr-cli-agents/agents.yml
44
+ * Agent registry lives in the agents module (not an IDE skill):
45
+ * .agent/skills/total-recall/modules/agents/agents.yml
40
46
  */
41
47
 
42
48
  // ─── Default agent registry (used when agents.yml doesn't exist) ─────────────
43
49
 
44
50
  const DEFAULT_AGENTS = [
45
51
  { name: 'antigravity', binary: 'antigravity', flags: '--sandbox=false --yolo -o json', priority: 1, enabled: true, exec: 'flag' },
52
+ { name: 'grok', binary: 'grok', flags: '--output-format plain --always-approve --permission-mode bypassPermissions', priority: 2, enabled: true, exec: 'flag' },
46
53
  { name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions --setting-sources local --tools ""', priority: 3, enabled: true, exec: 'flag' },
47
54
  { name: 'codex', binary: 'codex', flags: '--full-auto --json --skip-git-repo-check', priority: 4, enabled: true, exec: 'subcommand' },
48
55
  ];
@@ -51,14 +58,13 @@ const DEFAULT_AGENTS = [
51
58
  * Resolve the path to the cli-agents config file.
52
59
  */
53
60
  function getAgentsConfigPath() {
54
- // The cli-agents skill was namespaced to tr-cli-agents; check the new name
55
- // first, then fall back to the legacy name for repos initialized before the
56
- // rename, in both the nested (meta-skill) and standalone locations.
61
+ // Prefer modules/agents (TR_CORE_FOCUS). Fall back to legacy nested-skill paths.
57
62
  const candidates = [
63
+ path.join(agentDir, 'skills', 'total-recall', 'modules', 'agents', 'agents.yml'),
58
64
  path.join(agentDir, 'skills', 'total-recall', 'skills', 'tr-cli-agents', 'agents.yml'),
59
65
  path.join(agentDir, 'skills', 'total-recall', 'skills', 'cli-agents', 'agents.yml'),
60
66
  path.join(agentDir, 'skills', 'tr-cli-agents', 'agents.yml'),
61
- path.join(agentDir, 'skills', 'cli-agents', 'agents.yml')
67
+ path.join(agentDir, 'skills', 'cli-agents', 'agents.yml'),
62
68
  ];
63
69
  for (const p of candidates) {
64
70
  if (fs.existsSync(p)) return p;
@@ -200,7 +206,7 @@ export async function checkRuntimeHealth(config) {
200
206
  if (available.length === 0) {
201
207
  return {
202
208
  status: 'degraded',
203
- reason: 'No CLI agents found. Install antigravity, claude, or codex.',
209
+ reason: 'No CLI agents found. Install antigravity, grok, claude, or codex.',
204
210
  available: [],
205
211
  };
206
212
  }
@@ -290,7 +296,7 @@ export async function callLocalRuntime(prompt, system, config) {
290
296
  });
291
297
 
292
298
  if (!agent) {
293
- throw new Error('No CLI agents available. Install antigravity, claude, or codex.');
299
+ throw new Error('No CLI agents available. Install antigravity, grok, claude, or codex.');
294
300
  }
295
301
 
296
302
  const modelFlag = agent.model ? `-m ${agent.model}` : '';
@@ -299,6 +305,7 @@ export async function callLocalRuntime(prompt, system, config) {
299
305
  if (agent.exec === 'subcommand') {
300
306
  cmd = `${agent.binaryPath} exec "${escapeShell(fullPrompt)}" ${agent.flags} ${modelFlag}`.trim();
301
307
  } else {
308
+ // flag-style CLIs (antigravity, grok, claude, gemini): -p / --single prompt
302
309
  cmd = `${agent.binaryPath} -p "${escapeShell(fullPrompt)}" ${agent.flags} ${modelFlag}`.trim();
303
310
  }
304
311
 
@@ -311,12 +318,29 @@ export async function callLocalRuntime(prompt, system, config) {
311
318
  // Pre-flight command execution validation
312
319
  validateCommand(cmd);
313
320
 
321
+ const xaiKey =
322
+ process.env.XAI_API_KEY ||
323
+ process.env.GROK_API_KEY ||
324
+ dynamicSecrets.XAI_API_KEY ||
325
+ dynamicSecrets.xai_api_key ||
326
+ dynamicSecrets.GROK_API_KEY ||
327
+ dynamicSecrets.grok_api_key ||
328
+ '';
329
+
314
330
  const spawnEnv = {
315
331
  ...process.env,
332
+ // Ensure common CLI install dirs are visible (e.g. ~/.local/bin/grok)
333
+ PATH: [
334
+ path.join(os.homedir(), '.local', 'bin'),
335
+ path.join(os.homedir(), '.grok', 'bin'),
336
+ process.env.PATH || '',
337
+ ].filter(Boolean).join(path.delimiter),
316
338
  GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY || dynamicSecrets.google_api_key || googleApiKey,
317
339
  GEMINI_API_KEY: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || dynamicSecrets.google_api_key || googleApiKey,
318
340
  ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || dynamicSecrets.anthropic_api_key,
319
341
  OPENAI_API_KEY: process.env.OPENAI_API_KEY || dynamicSecrets.openai_api_key,
342
+ XAI_API_KEY: xaiKey || process.env.XAI_API_KEY,
343
+ GROK_API_KEY: xaiKey || process.env.GROK_API_KEY,
320
344
  TAVILY_API_KEY: process.env.TAVILY_API_KEY || dynamicSecrets.tavily_api_key || tavilyApiKey,
321
345
  BRAVE_API_KEY: process.env.BRAVE_API_KEY || dynamicSecrets.brave_api_key || braveApiKey,
322
346
  EXA_API_KEY: process.env.EXA_API_KEY || dynamicSecrets.exa_api_key || exaApiKey,
@@ -3,6 +3,7 @@ import path from 'path';
3
3
  import matter from 'gray-matter';
4
4
  import crypto from 'crypto';
5
5
  import { atomicWrite, safeStringify } from './vault.mjs';
6
+ import config from './config.mjs';
6
7
  import { getNodes } from './vault-cache.mjs';
7
8
  import { logger } from './logger.mjs';
8
9
  import { loadQueue, updateQueueItem } from './research-queue.mjs';
@@ -124,7 +125,7 @@ export function loadPendingTasks(queueDir) {
124
125
  /**
125
126
  * Update a task's status on disk.
126
127
  */
127
- export function updateTaskStatus(task, newStatus, queueDir) {
128
+ export function updateTaskStatus(task, newStatus, queueDir, lastError = null) {
128
129
  const filepath = task._filepath || path.join(queueDir, `${task.slug}.md`);
129
130
  if (!fs.existsSync(filepath)) return;
130
131
 
@@ -134,6 +135,9 @@ export function updateTaskStatus(task, newStatus, queueDir) {
134
135
  if (newStatus === 'completed') {
135
136
  data.completed_at = new Date().toISOString();
136
137
  }
138
+ if (lastError) {
139
+ data.last_error = lastError;
140
+ }
137
141
 
138
142
  // Also update in-memory object properties so they stay in sync
139
143
  task.status = newStatus;
@@ -252,7 +256,7 @@ function generateClarityReviewTask(vaultDir) {
252
256
  const target = nodes[Math.floor(Math.random() * nodes.length)];
253
257
  return {
254
258
  type: 'task',
255
- slug: `clarity-review-${crypto.randomBytes(4).toString('hex')}`,
259
+ slug: `clarity-review-${crypto.createHash('md5').update(target.slug).digest('hex').slice(0, 8)}`,
256
260
  priority: 30,
257
261
  category: 'memory-maintenance',
258
262
  target: target.slug,
@@ -269,7 +273,7 @@ function generateClarityReviewTask(vaultDir) {
269
273
  function generateMemoryCompactionTask(vaultDir) {
270
274
  return {
271
275
  type: 'task',
272
- slug: `memory-compaction-${crypto.randomBytes(4).toString('hex')}`,
276
+ slug: `memory-compaction-${new Date().toISOString().slice(0, 10)}`,
273
277
  priority: 35,
274
278
  category: 'memory-maintenance',
275
279
  target: 'global',
@@ -459,6 +463,41 @@ export function createScheduler({ queueDir, vaultDir, sessionsDir }) {
459
463
  queue.enqueue(task);
460
464
  }
461
465
 
466
+ // Optional remote vault sync job (env-configured only)
467
+ try {
468
+ const { remoteVaultSync } = config;
469
+ if (remoteVaultSync.enabled) {
470
+ const statusFile = path.join(path.dirname(remoteVaultSync.vaultDir), 'sync-status.json');
471
+ let lastRunMs = 0;
472
+ if (fs.existsSync(statusFile)) {
473
+ try {
474
+ const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
475
+ lastRunMs = new Date(status.lastRunAt || 0).getTime();
476
+ } catch {}
477
+ }
478
+
479
+ const SYNC_COOLDOWN_MS = remoteVaultSync.intervalMinutes * 60000;
480
+ if (Date.now() - lastRunMs >= SYNC_COOLDOWN_MS) {
481
+ queue.enqueue({
482
+ type: 'task',
483
+ slug: `remote-vault-sync-${Date.now().toString(36)}`,
484
+ priority: 90,
485
+ category: 'memory-maintenance',
486
+ status: 'pending',
487
+ created_by: 'scheduler',
488
+ reason: 'Scheduled remote vault sync.',
489
+ body: 'Run remote vault content synchronization.',
490
+ _is_remote_vault_sync: true,
491
+ });
492
+ }
493
+ }
494
+ } catch (err) {
495
+ logger.error({
496
+ subsystem: 'scheduler',
497
+ message: `Failed to load remote vault sync: ${err.message}`,
498
+ });
499
+ }
500
+
462
501
  // Load pending research queue tasks
463
502
  try {
464
503
  const COOLDOWN_MS = process.env.RESEARCH_COOLDOWN_MS
@@ -574,12 +613,27 @@ export function createScheduler({ queueDir, vaultDir, sessionsDir }) {
574
613
  },
575
614
 
576
615
  /**
577
- * Get the next task. If the queue is empty, generate an idle task.
616
+ * Get the next task.
617
+ *
618
+ * Default: only explicit (and research-queue) tasks — never invent work.
619
+ * Idle generation requires TR_IDLE_TASKS=1 or opts.allowIdle === true.
620
+ *
621
+ * @param {{ allowIdle?: boolean }} [opts]
622
+ * @returns {{ task: object|null, source: 'explicit'|'idle'|'empty' }}
578
623
  */
579
- next() {
624
+ next(opts = {}) {
580
625
  const explicit = queue.dequeue();
581
626
  if (explicit) return { task: explicit, source: 'explicit' };
582
627
 
628
+ const allowIdle =
629
+ opts.allowIdle === true ||
630
+ process.env.TR_IDLE_TASKS === '1' ||
631
+ process.env.TR_IDLE_TASKS === 'true';
632
+
633
+ if (!allowIdle) {
634
+ return { task: null, source: 'empty' };
635
+ }
636
+
583
637
  const idle = generateIdleTask({ vaultDir, sessionsDir });
584
638
  persistTaskToDisk(idle, queueDir);
585
639
  return { task: idle, source: 'idle' };
@@ -272,6 +272,19 @@ export const SchemaProposalSchema = z.object({
272
272
  reviewed_by: z.string().nullable().optional(),
273
273
  });
274
274
 
275
+ /** SSSS 0.9 meta-primitive for runtime-authored definitions (package core). */
276
+ export const PrimitiveSchema = z.object({
277
+ type: z.literal('primitive'),
278
+ title: z.string(),
279
+ description: z.string(),
280
+ timestamp: ssssDatetime(),
281
+ primitive_id: z.string().optional(),
282
+ namespace: z.string().optional(),
283
+ name: z.string().optional(),
284
+ version: z.union([z.number(), z.string()]).optional(),
285
+ portability: z.enum(['structural', 'tenant_private', 'resource_bound']).optional(),
286
+ }).passthrough();
287
+
275
288
  export const MigrationSchema = z.object({
276
289
  type: z.literal('migration'),
277
290
  migration_id: z.string(),
@@ -875,6 +888,7 @@ export const SSSS_SCHEMAS = {
875
888
  model: ModelSchema,
876
889
  conversation: ConversationSchema,
877
890
  run: RunSchema,
891
+ primitive: PrimitiveSchema,
878
892
  proposal: ProposalSchema,
879
893
  'schema-proposal': SchemaProposalSchema,
880
894
  migration: MigrationSchema,
@@ -2,7 +2,7 @@
2
2
  * src/core/search.mjs
3
3
  *
4
4
  * Canonical semantic search logic — searches vault nodes and session history
5
- * by meaning via Ollama embeddings. Called by both the MCP layer and REST API.
5
+ * by meaning via embedding vector similarity. Called by both the MCP layer and REST API.
6
6
  *
7
7
  * MCP and REST are thin wrappers; all logic lives here.
8
8
  */