total-recall-brain 3.3.0 → 3.4.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.
- package/README.md +1 -1
- package/extension/background.js +13 -1
- package/extension/content-script.js +18 -1
- package/extension/lib/brain-client.js +18 -5
- package/extension/options/options.js +9 -4
- package/frontend/index.html +8 -0
- package/frontend/src/api.ts +1 -2
- package/frontend/src/pages/ChatPage.tsx +9 -6
- package/frontend/src/pages/IntegrationsPage.tsx +4 -2
- package/package.json +2 -2
- package/scaffold/.agent/skills/total-recall/SKILL.md +55 -6
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +2 -2
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +1 -6
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +19 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +33 -5
- package/src/cli/forget.mjs +18 -12
- package/src/cli/ingest/index.mjs +3 -2
- package/src/cli/map.mjs +2 -2
- package/src/cli/rebuild.mjs +1 -1
- package/src/cli/remember.mjs +21 -11
- package/src/cli/share.mjs +7 -2
- package/src/core/append-log.mjs +131 -0
- package/src/core/clarity-rewriter.mjs +10 -6
- package/src/core/conclusion-writer.mjs +3 -2
- package/src/core/config.mjs +6 -0
- package/src/core/conflict-detector.mjs +4 -2
- package/src/core/context-compiler.mjs +412 -0
- package/src/core/daemon-loop.mjs +15 -0
- package/src/core/dream.mjs +4 -4
- package/src/core/drift-detector.mjs +3 -3
- package/src/core/embeddings.mjs +60 -10
- package/src/core/fact-seeker.mjs +70 -44
- package/src/core/import-rules.mjs +3 -2
- package/src/core/inference-engine.mjs +5 -4
- package/src/core/migrate.mjs +4 -3
- package/src/core/optimizer.mjs +6 -5
- package/src/core/parallel-context.mjs +288 -0
- package/src/core/post-mortem.mjs +4 -3
- package/src/core/research-queue.mjs +4 -3
- package/src/core/runtime.mjs +2 -2
- package/src/core/sandbox.mjs +56 -26
- package/src/core/scheduler.mjs +6 -5
- package/src/core/schema.mjs +2 -2
- package/src/core/search.mjs +167 -101
- package/src/core/surface.mjs +203 -53
- package/src/core/validated-write.mjs +100 -0
- package/src/core/vault-cache.mjs +46 -18
- package/src/core/vault.mjs +17 -29
- package/src/core/vector-field.mjs +659 -0
- package/src/core/vector-store.mjs +85 -0
- package/src/server/api.mjs +4 -3
- package/src/server/index.mjs +13 -61
- package/src/server/rest.mjs +233 -62
- package/src/server/route-manifest.json +344 -77
- package/src/server/routes/memory.mjs +85 -39
- package/src/server/routes/sessions.mjs +6 -2
- package/src/server/routes/share.mjs +9 -2
|
@@ -1,79 +1,346 @@
|
|
|
1
1
|
[
|
|
2
|
-
{
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
{
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
{
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
{
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
{
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
{
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
2
|
+
{
|
|
3
|
+
"method": "GET",
|
|
4
|
+
"path": "/.well-known/total-recall.json"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
"method": "GET",
|
|
8
|
+
"path": "/api"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"method": "GET",
|
|
12
|
+
"path": "/api/brain/export"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"method": "GET",
|
|
16
|
+
"path": "/api/brains"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"method": "GET",
|
|
20
|
+
"path": "/api/brains/:id/nodes"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"method": "POST",
|
|
24
|
+
"path": "/api/capture/:source"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"method": "GET",
|
|
28
|
+
"path": "/api/config"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"method": "GET",
|
|
32
|
+
"path": "/api/config-json"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"method": "POST",
|
|
36
|
+
"path": "/api/config-json"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"method": "GET",
|
|
40
|
+
"path": "/api/config/:name"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"method": "PUT",
|
|
44
|
+
"path": "/api/config/:name"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"method": "GET",
|
|
48
|
+
"path": "/api/conflicts"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"method": "POST",
|
|
52
|
+
"path": "/api/conflicts/resolve"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"method": "POST",
|
|
56
|
+
"path": "/api/context"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"method": "GET",
|
|
60
|
+
"path": "/api/context/flash/health"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"method": "GET",
|
|
64
|
+
"path": "/api/context/preview"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"method": "POST",
|
|
68
|
+
"path": "/api/context/stream"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"method": "POST",
|
|
72
|
+
"path": "/api/dream"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"method": "GET",
|
|
76
|
+
"path": "/api/extension/download"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"method": "GET",
|
|
80
|
+
"path": "/api/extension/status"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"method": "POST",
|
|
84
|
+
"path": "/api/field/compile"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"method": "POST",
|
|
88
|
+
"path": "/api/field/sample"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"method": "GET",
|
|
92
|
+
"path": "/api/field/stats"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"method": "GET",
|
|
96
|
+
"path": "/api/files"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"method": "GET",
|
|
100
|
+
"path": "/api/gemini-models"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"method": "GET",
|
|
104
|
+
"path": "/api/graph"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"method": "GET",
|
|
108
|
+
"path": "/api/import/rules"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"method": "POST",
|
|
112
|
+
"path": "/api/import/rules"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"method": "GET",
|
|
116
|
+
"path": "/api/instructions"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"method": "PUT",
|
|
120
|
+
"path": "/api/instructions"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"method": "GET",
|
|
124
|
+
"path": "/api/integrations/active"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"method": "POST",
|
|
128
|
+
"path": "/api/integrations/connect"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"method": "GET",
|
|
132
|
+
"path": "/api/keys"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"method": "POST",
|
|
136
|
+
"path": "/api/keys"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"method": "DELETE",
|
|
140
|
+
"path": "/api/keys/:id"
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"method": "GET",
|
|
144
|
+
"path": "/api/logs/:type"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"method": "GET",
|
|
148
|
+
"path": "/api/memory"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"method": "POST",
|
|
152
|
+
"path": "/api/memory"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"method": "DELETE",
|
|
156
|
+
"path": "/api/memory/:slug"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"method": "GET",
|
|
160
|
+
"path": "/api/memory/:slug"
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
"method": "PATCH",
|
|
164
|
+
"path": "/api/memory/:slug"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"method": "PUT",
|
|
168
|
+
"path": "/api/memory/:slug"
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"method": "POST",
|
|
172
|
+
"path": "/api/memory/search/semantic"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"method": "GET",
|
|
176
|
+
"path": "/api/memory/stats"
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
"method": "GET",
|
|
180
|
+
"path": "/api/research"
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"method": "POST",
|
|
184
|
+
"path": "/api/research"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
"method": "DELETE",
|
|
188
|
+
"path": "/api/research/:id"
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"method": "PATCH",
|
|
192
|
+
"path": "/api/research/:id"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"method": "POST",
|
|
196
|
+
"path": "/api/sandbox"
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"method": "GET",
|
|
200
|
+
"path": "/api/scripts"
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
"method": "GET",
|
|
204
|
+
"path": "/api/scripts/:name"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"method": "PUT",
|
|
208
|
+
"path": "/api/scripts/:name"
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
"method": "POST",
|
|
212
|
+
"path": "/api/scripts/:name/run"
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"method": "GET",
|
|
216
|
+
"path": "/api/sessions"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"method": "DELETE",
|
|
220
|
+
"path": "/api/sessions/:id"
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"method": "GET",
|
|
224
|
+
"path": "/api/sessions/:id"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"method": "POST",
|
|
228
|
+
"path": "/api/sessions/ingest"
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"method": "POST",
|
|
232
|
+
"path": "/api/share"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"method": "GET",
|
|
236
|
+
"path": "/api/skills"
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"method": "DELETE",
|
|
240
|
+
"path": "/api/skills/:name"
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"method": "GET",
|
|
244
|
+
"path": "/api/skills/:name"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"method": "PUT",
|
|
248
|
+
"path": "/api/skills/:name"
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"method": "POST",
|
|
252
|
+
"path": "/api/skills/install"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"method": "GET",
|
|
256
|
+
"path": "/api/skills/search"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
"method": "GET",
|
|
260
|
+
"path": "/api/ssss"
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"method": "GET",
|
|
264
|
+
"path": "/api/ssss/instructions"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"method": "GET",
|
|
268
|
+
"path": "/api/ssss/references"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"method": "GET",
|
|
272
|
+
"path": "/api/ssss/references/:name"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"method": "GET",
|
|
276
|
+
"path": "/api/ssss/skill/ssss"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"method": "GET",
|
|
280
|
+
"path": "/api/ssss/spec"
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"method": "GET",
|
|
284
|
+
"path": "/api/tasks"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"method": "POST",
|
|
288
|
+
"path": "/api/tasks"
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
"method": "POST",
|
|
292
|
+
"path": "/api/tts"
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"method": "GET",
|
|
296
|
+
"path": "/api/tts/status"
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
"method": "GET",
|
|
300
|
+
"path": "/api/usage"
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"method": "POST",
|
|
304
|
+
"path": "/api/vault/compact"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
"method": "POST",
|
|
308
|
+
"path": "/api/vault/compile"
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
"method": "GET",
|
|
312
|
+
"path": "/api/vault/hash"
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"method": "GET",
|
|
316
|
+
"path": "/api/vault/status"
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
"method": "POST",
|
|
320
|
+
"path": "/auth/change-password"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
"method": "POST",
|
|
324
|
+
"path": "/auth/login"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
"method": "POST",
|
|
328
|
+
"path": "/auth/logout"
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
"method": "GET",
|
|
332
|
+
"path": "/auth/me"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"method": "POST",
|
|
336
|
+
"path": "/auth/setup"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"method": "GET",
|
|
340
|
+
"path": "/auth/status"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"method": "GET",
|
|
344
|
+
"path": "/v1/models"
|
|
345
|
+
}
|
|
79
346
|
]
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
import express from 'express';
|
|
14
14
|
import fs from 'node:fs';
|
|
15
15
|
import path from 'node:path';
|
|
16
|
-
import {
|
|
16
|
+
import { createMemoryNode, walkMd } from '../../core/vault.mjs';
|
|
17
|
+
import { writeNodeValidated } from '../../core/validated-write.mjs';
|
|
17
18
|
import { getNodes, invalidate } from '../../core/vault-cache.mjs';
|
|
18
19
|
import { requireAuth, requireScope } from '../auth.mjs';
|
|
19
20
|
import { compileSurface } from '../../core/surface.mjs';
|
|
@@ -35,6 +36,10 @@ import {
|
|
|
35
36
|
|
|
36
37
|
const router = express.Router();
|
|
37
38
|
|
|
39
|
+
/* ── OPT-6: debounce recompilation across rapid consecutive writes ── */
|
|
40
|
+
let recompileTimer = null;
|
|
41
|
+
const RECOMPILE_DEBOUNCE_MS = 2000; // 2-second quiet period
|
|
42
|
+
|
|
38
43
|
function nodes(vaultDir = VAULT_DIR) {
|
|
39
44
|
return getNodes(vaultDir);
|
|
40
45
|
}
|
|
@@ -44,51 +49,70 @@ function nodes(vaultDir = VAULT_DIR) {
|
|
|
44
49
|
* Auto-mutates the brain in real time when any fact is written, updated, or deleted!
|
|
45
50
|
*/
|
|
46
51
|
async function triggerMutation(node, vaultDir = VAULT_DIR) {
|
|
52
|
+
// 1. Semantic conflict detection runs IMMEDIATELY (lightweight, per-node)
|
|
47
53
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
inboxDir: path.join(BRAIN_DIR, 'memory-inbox'),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
} catch (conflictErr) {
|
|
58
|
-
// Non-fatal fallback
|
|
54
|
+
const existing = nodes(vaultDir);
|
|
55
|
+
if (node && node.type === 'memory') {
|
|
56
|
+
detectAndResolve(node, existing, {
|
|
57
|
+
vaultDir,
|
|
58
|
+
inboxDir: path.join(BRAIN_DIR, 'memory-inbox'),
|
|
59
|
+
});
|
|
59
60
|
}
|
|
61
|
+
} catch (conflictErr) {
|
|
62
|
+
// Non-fatal fallback
|
|
63
|
+
}
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// 3. Rebuild dense embeddings index incrementally in background
|
|
65
|
+
// 2+3. Debounce the EXPENSIVE recompile + embeddings rebuild.
|
|
66
|
+
// Rapid consecutive writes accumulate; only ONE compile fires
|
|
67
|
+
// after a quiet period. Vault-cache is already invalidated
|
|
68
|
+
// immediately at each call-site via invalidate().
|
|
69
|
+
if (recompileTimer) clearTimeout(recompileTimer);
|
|
70
|
+
recompileTimer = setTimeout(async () => {
|
|
71
|
+
recompileTimer = null;
|
|
70
72
|
try {
|
|
71
|
-
|
|
72
|
-
await
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// Recompile instructions surface
|
|
74
|
+
await compileSurface({
|
|
75
|
+
vaultDir,
|
|
76
|
+
skillsDir: SKILLS_DIR,
|
|
77
|
+
derivedDir: DERIVED_DIR,
|
|
78
|
+
instructionsFile: INSTRUCTIONS,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// Rebuild dense embeddings index
|
|
82
|
+
try {
|
|
83
|
+
const vaultNodes = nodes(vaultDir);
|
|
84
|
+
await buildEmbeddingsIndex(vaultNodes, DERIVED_DIR);
|
|
85
|
+
await buildSessionEmbeddingsIndex(SESSIONS_DIR, DERIVED_DIR);
|
|
86
|
+
} catch (embedErr) {
|
|
87
|
+
// Ollama/embeddings offline non-fatal
|
|
88
|
+
}
|
|
89
|
+
} catch (err) {
|
|
90
|
+
// Non-fatal background log
|
|
76
91
|
}
|
|
77
|
-
}
|
|
78
|
-
// Non-fatal background log
|
|
79
|
-
}
|
|
92
|
+
}, RECOMPILE_DEBOUNCE_MS);
|
|
80
93
|
}
|
|
81
94
|
|
|
82
95
|
|
|
83
96
|
// SSSS v2 frontmatter fields we pass through verbatim from request bodies.
|
|
84
|
-
const PASSTHROUGH_FIELDS = [
|
|
97
|
+
const PASSTHROUGH_FIELDS = [
|
|
98
|
+
'priority',
|
|
99
|
+
'modality',
|
|
100
|
+
'confidence',
|
|
101
|
+
'importance',
|
|
102
|
+
'status',
|
|
103
|
+
'related',
|
|
104
|
+
'sources',
|
|
105
|
+
'supersedes',
|
|
106
|
+
'superseded_by',
|
|
107
|
+
'contradicts',
|
|
108
|
+
];
|
|
85
109
|
|
|
86
110
|
router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
87
111
|
try {
|
|
88
112
|
const vaultDir = resolveVaultFromQuery(req);
|
|
89
113
|
let list = nodes(vaultDir);
|
|
90
114
|
|
|
91
|
-
const { q, category, tag, limit = '200', offset = '0' } = req.query;
|
|
115
|
+
const { q, category, tag, status, limit = '200', offset = '0' } = req.query;
|
|
92
116
|
|
|
93
117
|
if (q) {
|
|
94
118
|
const query = String(q).toLowerCase();
|
|
@@ -98,6 +122,7 @@ router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) =
|
|
|
98
122
|
);
|
|
99
123
|
}
|
|
100
124
|
if (category) list = list.filter(n => n.category === category);
|
|
125
|
+
if (status) list = list.filter(n => n.status === status);
|
|
101
126
|
if (tag) list = list.filter(n => (n.tags || []).includes(tag));
|
|
102
127
|
|
|
103
128
|
const total = list.length;
|
|
@@ -154,8 +179,15 @@ router.post('/api/memory', requireAuth, requireScope('memory:write'), (req, res)
|
|
|
154
179
|
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
155
180
|
}
|
|
156
181
|
|
|
157
|
-
|
|
158
|
-
|
|
182
|
+
const vaultResult = writeNodeValidated(node, vaultDir);
|
|
183
|
+
if (!vaultResult.success) {
|
|
184
|
+
return res.status(422).json({
|
|
185
|
+
error: 'Validation failed',
|
|
186
|
+
validation: vaultResult.validation,
|
|
187
|
+
repair: vaultResult.repair,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
invalidate(vaultDir);
|
|
159
191
|
triggerMutation(node, vaultDir);
|
|
160
192
|
res.status(201).json(sanitizeNode(node));
|
|
161
193
|
} catch (err) {
|
|
@@ -178,8 +210,15 @@ router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req,
|
|
|
178
210
|
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
179
211
|
}
|
|
180
212
|
|
|
181
|
-
|
|
182
|
-
|
|
213
|
+
const vaultResult = writeNodeValidated(node, vaultDir);
|
|
214
|
+
if (!vaultResult.success) {
|
|
215
|
+
return res.status(422).json({
|
|
216
|
+
error: 'Validation failed',
|
|
217
|
+
validation: vaultResult.validation,
|
|
218
|
+
repair: vaultResult.repair,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
invalidate(vaultDir);
|
|
183
222
|
triggerMutation(node, vaultDir);
|
|
184
223
|
res.json(sanitizeNode(node));
|
|
185
224
|
} catch (err) {
|
|
@@ -202,7 +241,7 @@ router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (re
|
|
|
202
241
|
content: actualContent,
|
|
203
242
|
});
|
|
204
243
|
updated.tags = tags ?? existing.tags ?? [];
|
|
205
|
-
updated.
|
|
244
|
+
updated.created = existing.created;
|
|
206
245
|
|
|
207
246
|
for (const key of PASSTHROUGH_FIELDS) {
|
|
208
247
|
if (existing[key] !== undefined) updated[key] = existing[key];
|
|
@@ -211,8 +250,15 @@ router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (re
|
|
|
211
250
|
if (req.body[key] !== undefined) updated[key] = req.body[key];
|
|
212
251
|
}
|
|
213
252
|
|
|
214
|
-
|
|
215
|
-
|
|
253
|
+
const vaultResult = writeNodeValidated(updated, vaultDir);
|
|
254
|
+
if (!vaultResult.success) {
|
|
255
|
+
return res.status(422).json({
|
|
256
|
+
error: 'Validation failed',
|
|
257
|
+
validation: vaultResult.validation,
|
|
258
|
+
repair: vaultResult.repair,
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
invalidate(vaultDir);
|
|
216
262
|
triggerMutation(updated, vaultDir);
|
|
217
263
|
res.json(sanitizeNode(updated));
|
|
218
264
|
} catch (err) {
|
|
@@ -238,7 +284,7 @@ router.delete('/api/memory/:slug', requireAuth, requireScope('memory:write'), (r
|
|
|
238
284
|
}
|
|
239
285
|
}
|
|
240
286
|
}
|
|
241
|
-
invalidate();
|
|
287
|
+
invalidate(vaultDir);
|
|
242
288
|
triggerMutation(null, vaultDir);
|
|
243
289
|
res.json({ deleted: true, slug: req.params.slug });
|
|
244
290
|
} catch (err) {
|
|
@@ -205,7 +205,12 @@ router.get('/api/sessions/:id', requireAuth, requireScope('memory:read'), (req,
|
|
|
205
205
|
router.post('/api/sessions/ingest', ingestRateLimiter(), requireAuth, requireScope('memory:write'), (req, res) => {
|
|
206
206
|
try {
|
|
207
207
|
const body = req.body || {};
|
|
208
|
-
const source = body.source || 'api'
|
|
208
|
+
const source = String(body.source || 'api')
|
|
209
|
+
.toLowerCase()
|
|
210
|
+
.replace(/[^a-z0-9_-]/g, '-')
|
|
211
|
+
.replace(/-+/g, '-')
|
|
212
|
+
.replace(/^-|-$/g, '')
|
|
213
|
+
.slice(0, 48) || 'api';
|
|
209
214
|
|
|
210
215
|
let messages;
|
|
211
216
|
if (body.content && typeof body.content === 'string') {
|
|
@@ -333,4 +338,3 @@ router.delete('/api/sessions/:id', requireAuth, requireScope('memory:write'), (r
|
|
|
333
338
|
});
|
|
334
339
|
|
|
335
340
|
export { router as sessionsRouter, drainActiveEmbeddings };
|
|
336
|
-
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
|
|
9
9
|
import express from 'express';
|
|
10
10
|
import crypto from 'node:crypto';
|
|
11
|
-
import { createMemoryNode
|
|
11
|
+
import { createMemoryNode } from '../../core/vault.mjs';
|
|
12
|
+
import { writeNodeValidated } from '../../core/validated-write.mjs';
|
|
12
13
|
import { invalidate } from '../../core/vault-cache.mjs';
|
|
13
14
|
import { addToQueue } from '../../core/research-queue.mjs';
|
|
14
15
|
import { requireAuth, requireScope } from '../auth.mjs';
|
|
@@ -121,7 +122,13 @@ router.post('/api/share', requireAuth, requireScope('memory:write'), async (req,
|
|
|
121
122
|
}];
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
const vaultResult = writeNodeValidated(node, vaultDir);
|
|
126
|
+
if (!vaultResult.success) {
|
|
127
|
+
return res.status(422).json({
|
|
128
|
+
error: 'Validation failed',
|
|
129
|
+
validation: vaultResult.validation,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
125
132
|
invalidate();
|
|
126
133
|
|
|
127
134
|
// Mark extension as connected when share comes from the extension
|