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.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/src/core/runtime.mjs
CHANGED
|
@@ -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
|
|
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
|
|
39
|
-
* .agent/skills/total-recall/
|
|
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
|
-
//
|
|
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,
|
package/src/core/scheduler.mjs
CHANGED
|
@@ -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.
|
|
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-${
|
|
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.
|
|
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' };
|
package/src/core/schema.mjs
CHANGED
|
@@ -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,
|
package/src/core/search.mjs
CHANGED
|
@@ -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
|
|
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
|
*/
|