total-recall-brain 3.12.2 → 3.13.1
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 +3 -1
- package/frontend/src/App.tsx +112 -111
- package/frontend/src/api.ts +700 -18
- 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 +78 -0
- 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 +614 -0
- 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 +583 -0
- 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/ModelsPage.tsx +280 -23
- package/frontend/src/pages/OkfPage.tsx +535 -0
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +561 -0
- package/frontend/src/pages/SettingsPage.tsx +826 -740
- package/frontend/src/pages/SkillsPage.tsx +1259 -117
- package/frontend/src/pages/UsagePage.tsx +256 -11
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +173 -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 +19 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +28 -16
- package/src/server/auth.mjs +53 -2
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +405 -68
- package/src/server/route-manifest.json +168 -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
|
@@ -14,12 +14,13 @@ import express from 'express';
|
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { createMemoryNode, walkMd } from '../../core/vault.mjs';
|
|
17
|
-
import {
|
|
17
|
+
import { writeNodeValidatedAsync } from '../../core/validated-write.mjs';
|
|
18
18
|
import { getNodes, invalidate } from '../../core/vault-cache.mjs';
|
|
19
19
|
import { requireAuth, requireScope } from '../auth.mjs';
|
|
20
20
|
import { compileSurface } from '../../core/surface.mjs';
|
|
21
21
|
import { buildEmbeddingsIndex, buildSessionEmbeddingsIndex } from '../../core/embeddings.mjs';
|
|
22
22
|
import { detectAndResolve } from '../../core/conflict-detector.mjs';
|
|
23
|
+
import { listQueue, updateQueueItem } from '../../core/research-queue.mjs';
|
|
23
24
|
import {
|
|
24
25
|
BRAIN_DIR,
|
|
25
26
|
VAULT_DIR,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
serverError,
|
|
33
34
|
sanitizeNode,
|
|
34
35
|
resolveVaultFromQuery,
|
|
36
|
+
resolveAllVaultsFromQuery,
|
|
35
37
|
} from './_shared.mjs';
|
|
36
38
|
|
|
37
39
|
const router = express.Router();
|
|
@@ -109,10 +111,23 @@ const PASSTHROUGH_FIELDS = [
|
|
|
109
111
|
|
|
110
112
|
router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
111
113
|
try {
|
|
112
|
-
const
|
|
113
|
-
let list =
|
|
114
|
+
const vaultDirs = resolveAllVaultsFromQuery(req);
|
|
115
|
+
let list = [];
|
|
114
116
|
|
|
115
|
-
|
|
117
|
+
// Track unique slugs so we don't count shadowing duplicates twice
|
|
118
|
+
const seenSlugs = new Set();
|
|
119
|
+
|
|
120
|
+
// Iterate backwards so most specific (last in list) is seen first
|
|
121
|
+
for (let i = vaultDirs.length - 1; i >= 0; i--) {
|
|
122
|
+
const vaultList = nodes(vaultDirs[i]);
|
|
123
|
+
for (const n of vaultList) {
|
|
124
|
+
if (!seenSlugs.has(n.slug)) {
|
|
125
|
+
seenSlugs.add(n.slug);
|
|
126
|
+
list.push(n);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const { q, category, tag, tags, status, limit = '200', offset = '0' } = req.query;
|
|
116
131
|
|
|
117
132
|
if (q) {
|
|
118
133
|
const query = String(q).toLowerCase();
|
|
@@ -123,7 +138,20 @@ router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) =
|
|
|
123
138
|
}
|
|
124
139
|
if (category) list = list.filter(n => n.category === category);
|
|
125
140
|
if (status) list = list.filter(n => n.status === status);
|
|
126
|
-
|
|
141
|
+
// Accept tag= or tags= (comma-separated). OpenWiki UI uses tags=openwiki.
|
|
142
|
+
const tagFilter = tag || tags;
|
|
143
|
+
if (tagFilter) {
|
|
144
|
+
const wanted = String(tagFilter)
|
|
145
|
+
.split(',')
|
|
146
|
+
.map((t) => t.trim().toLowerCase())
|
|
147
|
+
.filter(Boolean);
|
|
148
|
+
if (wanted.length) {
|
|
149
|
+
list = list.filter((n) => {
|
|
150
|
+
const nodeTags = (n.tags || []).map((t) => String(t).toLowerCase());
|
|
151
|
+
return wanted.every((t) => nodeTags.includes(t));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
127
155
|
|
|
128
156
|
const total = list.length;
|
|
129
157
|
const off = Math.max(0, parseInt(offset, 10) || 0);
|
|
@@ -138,30 +166,87 @@ router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) =
|
|
|
138
166
|
|
|
139
167
|
router.get('/api/memory/stats', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
140
168
|
try {
|
|
141
|
-
const
|
|
142
|
-
const list = nodes(vaultDir);
|
|
169
|
+
const vaultDirs = resolveAllVaultsFromQuery(req);
|
|
143
170
|
const byCategory = {};
|
|
144
|
-
|
|
145
|
-
|
|
171
|
+
let total = 0;
|
|
172
|
+
|
|
173
|
+
// Track unique slugs so we don't count shadowing duplicates twice
|
|
174
|
+
const seenSlugs = new Set();
|
|
175
|
+
|
|
176
|
+
// Iterate backwards so most specific (last in list) is seen first
|
|
177
|
+
for (let i = vaultDirs.length - 1; i >= 0; i--) {
|
|
178
|
+
const list = nodes(vaultDirs[i]);
|
|
179
|
+
for (const n of list) {
|
|
180
|
+
if (!seenSlugs.has(n.slug)) {
|
|
181
|
+
seenSlugs.add(n.slug);
|
|
182
|
+
byCategory[n.category] = (byCategory[n.category] || 0) + 1;
|
|
183
|
+
total++;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
146
186
|
}
|
|
147
|
-
|
|
187
|
+
|
|
188
|
+
res.json({ total, by_category: byCategory });
|
|
148
189
|
} catch (err) {
|
|
149
190
|
serverError(res, err);
|
|
150
191
|
}
|
|
151
192
|
});
|
|
152
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Expand vault search: query brain(s) first, then global, then every registered
|
|
196
|
+
* project brain. Fixes dashboard 404s when the list came from a project vault
|
|
197
|
+
* but GET omitted ?brain=.
|
|
198
|
+
*/
|
|
199
|
+
function vaultDirsForSlugLookup(req) {
|
|
200
|
+
const dirs = [...resolveAllVaultsFromQuery(req)];
|
|
201
|
+
const seen = new Set(dirs);
|
|
202
|
+
|
|
203
|
+
const push = (dir) => {
|
|
204
|
+
if (dir && !seen.has(dir) && fs.existsSync(dir)) {
|
|
205
|
+
seen.add(dir);
|
|
206
|
+
dirs.push(dir);
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
push(VAULT_DIR);
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
214
|
+
const registryPath = path.join(home, '.agent', 'skills', 'total-recall', 'config', 'project-registry.json');
|
|
215
|
+
if (fs.existsSync(registryPath)) {
|
|
216
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
217
|
+
if (Array.isArray(registry)) {
|
|
218
|
+
for (const project of registry) {
|
|
219
|
+
if (project?.brainDir) {
|
|
220
|
+
push(path.join(project.brainDir, 'memory-vault'));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// non-fatal
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return dirs;
|
|
230
|
+
}
|
|
231
|
+
|
|
153
232
|
router.get('/api/memory/:slug', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
154
233
|
try {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
234
|
+
const vaultDirs = vaultDirsForSlugLookup(req);
|
|
235
|
+
|
|
236
|
+
for (const vaultDir of vaultDirs) {
|
|
237
|
+
const node = nodes(vaultDir).find(n => n.slug === req.params.slug);
|
|
238
|
+
if (node) {
|
|
239
|
+
return res.json(sanitizeNode(node));
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
159
244
|
} catch (err) {
|
|
160
245
|
serverError(res, err);
|
|
161
246
|
}
|
|
162
247
|
});
|
|
163
248
|
|
|
164
|
-
router.post('/api/memory', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
249
|
+
router.post('/api/memory', requireAuth, requireScope('memory:write'), async (req, res) => {
|
|
165
250
|
try {
|
|
166
251
|
const vaultDir = resolveVaultFromQuery(req);
|
|
167
252
|
const { slug, title, category, content, body, tags } = req.body || {};
|
|
@@ -179,7 +264,7 @@ router.post('/api/memory', requireAuth, requireScope('memory:write'), (req, res)
|
|
|
179
264
|
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
180
265
|
}
|
|
181
266
|
|
|
182
|
-
const vaultResult =
|
|
267
|
+
const vaultResult = await writeNodeValidatedAsync(node, vaultDir);
|
|
183
268
|
if (!vaultResult.success) {
|
|
184
269
|
return res.status(422).json({
|
|
185
270
|
error: 'Validation failed',
|
|
@@ -195,7 +280,7 @@ router.post('/api/memory', requireAuth, requireScope('memory:write'), (req, res)
|
|
|
195
280
|
}
|
|
196
281
|
});
|
|
197
282
|
|
|
198
|
-
router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
283
|
+
router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), async (req, res) => {
|
|
199
284
|
try {
|
|
200
285
|
const vaultDir = resolveVaultFromQuery(req);
|
|
201
286
|
const { title, category, content, body, tags } = req.body || {};
|
|
@@ -210,7 +295,7 @@ router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req,
|
|
|
210
295
|
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
211
296
|
}
|
|
212
297
|
|
|
213
|
-
const vaultResult =
|
|
298
|
+
const vaultResult = await writeNodeValidatedAsync(node, vaultDir);
|
|
214
299
|
if (!vaultResult.success) {
|
|
215
300
|
return res.status(422).json({
|
|
216
301
|
error: 'Validation failed',
|
|
@@ -218,18 +303,28 @@ router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req,
|
|
|
218
303
|
repair: vaultResult.repair,
|
|
219
304
|
});
|
|
220
305
|
}
|
|
221
|
-
invalidate(
|
|
222
|
-
triggerMutation(node,
|
|
306
|
+
invalidate(targetVaultDir);
|
|
307
|
+
triggerMutation(node, targetVaultDir);
|
|
223
308
|
res.json(sanitizeNode(node));
|
|
224
309
|
} catch (err) {
|
|
225
310
|
serverError(res, err);
|
|
226
311
|
}
|
|
227
312
|
});
|
|
228
313
|
|
|
229
|
-
router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
314
|
+
router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), async (req, res) => {
|
|
230
315
|
try {
|
|
231
|
-
const
|
|
232
|
-
|
|
316
|
+
const vaultDirs = resolveAllVaultsFromQuery(req);
|
|
317
|
+
let existing;
|
|
318
|
+
let targetVaultDir = vaultDirs[vaultDirs.length - 1]; // Default to most specific
|
|
319
|
+
|
|
320
|
+
for (const vaultDir of vaultDirs) {
|
|
321
|
+
existing = nodes(vaultDir).find(n => n.slug === req.params.slug);
|
|
322
|
+
if (existing) {
|
|
323
|
+
targetVaultDir = vaultDir;
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
233
328
|
if (!existing) return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
234
329
|
|
|
235
330
|
const { title, category, content, body, tags } = req.body || {};
|
|
@@ -250,7 +345,7 @@ router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (re
|
|
|
250
345
|
if (req.body[key] !== undefined) updated[key] = req.body[key];
|
|
251
346
|
}
|
|
252
347
|
|
|
253
|
-
const vaultResult =
|
|
348
|
+
const vaultResult = await writeNodeValidatedAsync(updated, targetVaultDir);
|
|
254
349
|
if (!vaultResult.success) {
|
|
255
350
|
return res.status(422).json({
|
|
256
351
|
error: 'Validation failed',
|
|
@@ -258,8 +353,8 @@ router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (re
|
|
|
258
353
|
repair: vaultResult.repair,
|
|
259
354
|
});
|
|
260
355
|
}
|
|
261
|
-
invalidate(
|
|
262
|
-
triggerMutation(updated,
|
|
356
|
+
invalidate(targetVaultDir);
|
|
357
|
+
triggerMutation(updated, targetVaultDir);
|
|
263
358
|
res.json(sanitizeNode(updated));
|
|
264
359
|
} catch (err) {
|
|
265
360
|
serverError(res, err);
|
|
@@ -268,15 +363,24 @@ router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (re
|
|
|
268
363
|
|
|
269
364
|
router.delete('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
270
365
|
try {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
366
|
+
const vaultDirs = resolveAllVaultsFromQuery(req);
|
|
367
|
+
let node;
|
|
368
|
+
let targetVaultDir;
|
|
369
|
+
|
|
370
|
+
for (const vaultDir of vaultDirs) {
|
|
371
|
+
node = nodes(vaultDir).find(n => n.slug === req.params.slug);
|
|
372
|
+
if (node) {
|
|
373
|
+
targetVaultDir = vaultDir;
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
274
378
|
if (!node) return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
275
379
|
|
|
276
380
|
if (node._filePath && fs.existsSync(node._filePath)) {
|
|
277
381
|
fs.unlinkSync(node._filePath);
|
|
278
382
|
} else {
|
|
279
|
-
for (const file of walkMd(
|
|
383
|
+
for (const file of walkMd(targetVaultDir)) {
|
|
280
384
|
const raw = fs.readFileSync(file, 'utf8');
|
|
281
385
|
if (raw.includes(`slug: ${req.params.slug}`)) {
|
|
282
386
|
fs.unlinkSync(file);
|
|
@@ -284,8 +388,17 @@ router.delete('/api/memory/:slug', requireAuth, requireScope('memory:write'), (r
|
|
|
284
388
|
}
|
|
285
389
|
}
|
|
286
390
|
}
|
|
287
|
-
invalidate(
|
|
288
|
-
triggerMutation(null,
|
|
391
|
+
invalidate(targetVaultDir);
|
|
392
|
+
triggerMutation(null, targetVaultDir);
|
|
393
|
+
|
|
394
|
+
// Keep interface and queue in sync: clear this node_slug from any research agenda items
|
|
395
|
+
const queue = listQueue({});
|
|
396
|
+
for (const item of queue.items) {
|
|
397
|
+
if (item.node_slug === req.params.slug) {
|
|
398
|
+
updateQueueItem(item.id, { node_slug: null });
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
289
402
|
res.json({ deleted: true, slug: req.params.slug });
|
|
290
403
|
} catch (err) {
|
|
291
404
|
serverError(res, err);
|
|
@@ -53,3 +53,33 @@ researchRouter.delete('/api/research/:id', requireAuth, requireScope('memory:wri
|
|
|
53
53
|
serverError(res, err);
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
|
+
|
|
57
|
+
// ─── Optional remote vault sync ───────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
researchRouter.post('/api/sync/remote-vault/run', requireAuth, requireScope('memory:write'), async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const { runSync } = await import('../../core/remote-vault-sync.mjs');
|
|
62
|
+
runSync().catch((err) =>
|
|
63
|
+
logger.error('remote-vault-sync', 'Background sync failed', { error: err.message }),
|
|
64
|
+
);
|
|
65
|
+
res.json({ status: 'started' });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
serverError(res, err);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
researchRouter.get('/api/sync/remote-vault/status', requireAuth, requireScope('memory:read'), async (req, res) => {
|
|
72
|
+
try {
|
|
73
|
+
const fs = await import('node:fs');
|
|
74
|
+
const path = await import('node:path');
|
|
75
|
+
const { remoteVaultSync } = await import('../../core/config.mjs');
|
|
76
|
+
const statusFile = path.join(path.dirname(remoteVaultSync.vaultDir), 'sync-status.json');
|
|
77
|
+
if (!fs.existsSync(statusFile)) {
|
|
78
|
+
return res.json({ ok: false, error: 'Never run' });
|
|
79
|
+
}
|
|
80
|
+
const status = JSON.parse(fs.readFileSync(statusFile, 'utf8'));
|
|
81
|
+
res.json(status);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
serverError(res, err);
|
|
84
|
+
}
|
|
85
|
+
});
|