@pcircle/memesh 4.4.0 → 4.5.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.de.md +31 -8
- package/README.es.md +9 -5
- package/README.fr.md +29 -6
- package/README.ja.md +9 -5
- package/README.ko.md +10 -6
- package/README.md +14 -7
- package/README.pt.md +28 -5
- package/README.th.md +14 -4
- package/README.vi.md +30 -7
- package/README.zh-CN.md +10 -6
- package/README.zh-TW.md +10 -6
- package/dashboard/dist/index.html +10 -10
- package/dist/cli/view-live.js +1 -1
- package/dist/core/analytics.d.ts +4 -0
- package/dist/core/analytics.d.ts.map +1 -1
- package/dist/core/analytics.js +8 -8
- package/dist/core/analytics.js.map +1 -1
- package/dist/core/auto-tagger.d.ts.map +1 -1
- package/dist/core/auto-tagger.js.map +1 -1
- package/dist/core/config.d.ts +5 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +28 -13
- package/dist/core/config.js.map +1 -1
- package/dist/core/digest-validator.d.ts.map +1 -1
- package/dist/core/digest-validator.js +3 -1
- package/dist/core/digest-validator.js.map +1 -1
- package/dist/core/doctor.d.ts +2 -0
- package/dist/core/doctor.d.ts.map +1 -1
- package/dist/core/doctor.js +70 -60
- package/dist/core/doctor.js.map +1 -1
- package/dist/core/dreamer.d.ts +21 -1
- package/dist/core/dreamer.d.ts.map +1 -1
- package/dist/core/dreamer.js +86 -8
- package/dist/core/dreamer.js.map +1 -1
- package/dist/core/embedder.d.ts +1 -4
- package/dist/core/embedder.d.ts.map +1 -1
- package/dist/core/embedder.js +5 -95
- package/dist/core/embedder.js.map +1 -1
- package/dist/core/failure-analyzer.d.ts.map +1 -1
- package/dist/core/failure-analyzer.js +2 -1
- package/dist/core/failure-analyzer.js.map +1 -1
- package/dist/core/llm-client.d.ts.map +1 -1
- package/dist/core/llm-client.js.map +1 -1
- package/dist/core/llm-validator.d.ts +1 -0
- package/dist/core/llm-validator.d.ts.map +1 -1
- package/dist/core/llm-validator.js +33 -10
- package/dist/core/llm-validator.js.map +1 -1
- package/dist/core/operations.js +1 -1
- package/dist/core/operations.js.map +1 -1
- package/dist/core/output-language.d.ts +6 -0
- package/dist/core/output-language.d.ts.map +1 -0
- package/dist/core/output-language.js +25 -0
- package/dist/core/output-language.js.map +1 -0
- package/dist/core/patterns.d.ts +0 -1
- package/dist/core/patterns.d.ts.map +1 -1
- package/dist/core/patterns.js +1 -5
- package/dist/core/patterns.js.map +1 -1
- package/dist/core/transcript-extractor.d.ts +89 -0
- package/dist/core/transcript-extractor.d.ts.map +1 -0
- package/dist/core/transcript-extractor.js +437 -0
- package/dist/core/transcript-extractor.js.map +1 -0
- package/dist/core/transcript-source.d.ts +21 -0
- package/dist/core/transcript-source.d.ts.map +1 -0
- package/dist/core/transcript-source.js +142 -0
- package/dist/core/transcript-source.js.map +1 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +10 -0
- package/dist/db.js.map +1 -1
- package/dist/skills-manifest.json +4 -4
- package/dist/transports/cli/cli.d.ts.map +1 -1
- package/dist/transports/cli/cli.js +153 -8
- package/dist/transports/cli/cli.js.map +1 -1
- package/dist/transports/http/server.d.ts +7 -0
- package/dist/transports/http/server.d.ts.map +1 -1
- package/dist/transports/http/server.js +117 -50
- package/dist/transports/http/server.js.map +1 -1
- package/dist/transports/mcp/handlers.d.ts.map +1 -1
- package/dist/transports/mcp/handlers.js +2 -1
- package/dist/transports/mcp/handlers.js.map +1 -1
- package/package.json +1 -10
- package/skills/agentic-orchestration/SKILL.md +1 -1
- package/skills/memesh/SKILL.md +2 -0
|
@@ -7,6 +7,7 @@ import { openDatabase, closeDatabase, getDatabase } from '../../db.js';
|
|
|
7
7
|
import { remember, recallWithConflicts, forget, exportMemories, importMemories, learn } from '../../core/operations.js';
|
|
8
8
|
import { KnowledgeGraph } from '../../knowledge-graph.js';
|
|
9
9
|
import { logCapabilities, readConfig, updateConfig, detectCapabilities } from '../../core/config.js';
|
|
10
|
+
import { languageValueError } from '../../core/output-language.js';
|
|
10
11
|
import { computePatterns } from '../../core/patterns.js';
|
|
11
12
|
import { computeAnalytics, computePmAnalytics } from '../../core/analytics.js';
|
|
12
13
|
import { computeStats } from '../../core/stats.js';
|
|
@@ -14,7 +15,7 @@ import { computeProjects } from '../../core/projects.js';
|
|
|
14
15
|
import { computeGraph } from '../../core/graph.js';
|
|
15
16
|
import { verifyAgentWork } from '../../core/verifier.js';
|
|
16
17
|
import { RememberSchema as RememberBody, RecallSchema as RecallBody, ForgetSchema as ForgetBody, ExportSchema as ExportBody, ImportSchema as ImportBody, LearnSchema as LearnBody, VerifyAgentWorkSchema as VerifyBody, } from '../schemas.js';
|
|
17
|
-
import { getUpdateCheck } from '../../core/version-check.js';
|
|
18
|
+
import { checkForUpdate, getLastUpdateCheck, getUpdateCheck } from '../../core/version-check.js';
|
|
18
19
|
import { getCurrentInstallChannel, getInstallChannelSupport } from '../../core/install-channel.js';
|
|
19
20
|
import { getDbPath, getMemeshDirFromDbPath } from '../../core/paths.js';
|
|
20
21
|
import { RETIRED_ROUTES } from './retired-routes.js';
|
|
@@ -25,12 +26,23 @@ const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)
|
|
|
25
26
|
const packageRoot = path.dirname(packageJsonPath);
|
|
26
27
|
const packageVersion = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version ?? '0.0.0';
|
|
27
28
|
const app = express();
|
|
29
|
+
export function isLoopbackRequest(req) {
|
|
30
|
+
const ip = req.ip ?? '';
|
|
31
|
+
return ip === '127.0.0.1' || ip === '::1' || ip === '::ffff:127.0.0.1';
|
|
32
|
+
}
|
|
28
33
|
const apiLimiter = rateLimit({
|
|
29
34
|
windowMs: 15 * 60 * 1000,
|
|
30
35
|
max: 100,
|
|
31
36
|
standardHeaders: true,
|
|
32
37
|
legacyHeaders: false,
|
|
33
|
-
|
|
38
|
+
skip: (req) => isLoopbackRequest(req),
|
|
39
|
+
handler: (_req, res) => {
|
|
40
|
+
res.status(429).json({
|
|
41
|
+
success: false,
|
|
42
|
+
errorCode: 'rate.limited',
|
|
43
|
+
error: 'Too many requests in a short time. Wait a moment and try again.',
|
|
44
|
+
});
|
|
45
|
+
},
|
|
34
46
|
});
|
|
35
47
|
let remoteToken = null;
|
|
36
48
|
const serverAuthRequired = new WeakMap();
|
|
@@ -97,6 +109,7 @@ function bearerAuth(req, res, next) {
|
|
|
97
109
|
if (!remoteToken) {
|
|
98
110
|
res.status(503).json({
|
|
99
111
|
success: false,
|
|
112
|
+
errorCode: 'auth.not-configured',
|
|
100
113
|
error: 'remote bearer auth not configured on this server',
|
|
101
114
|
});
|
|
102
115
|
return;
|
|
@@ -105,17 +118,17 @@ function bearerAuth(req, res, next) {
|
|
|
105
118
|
const trimmed = header.trim();
|
|
106
119
|
const wsIndex = trimmed.search(/\s/);
|
|
107
120
|
if (wsIndex < 0 || trimmed.slice(0, wsIndex).toLowerCase() !== 'bearer') {
|
|
108
|
-
res.status(401).json({ success: false, error: 'Missing Authorization: Bearer <token>' });
|
|
121
|
+
res.status(401).json({ success: false, errorCode: 'auth.missing-bearer', error: 'Missing Authorization: Bearer <token>' });
|
|
109
122
|
return;
|
|
110
123
|
}
|
|
111
124
|
const tokenPart = trimmed.slice(wsIndex + 1).trim();
|
|
112
125
|
if (!tokenPart) {
|
|
113
|
-
res.status(401).json({ success: false, error: 'Missing Authorization: Bearer <token>' });
|
|
126
|
+
res.status(401).json({ success: false, errorCode: 'auth.missing-bearer', error: 'Missing Authorization: Bearer <token>' });
|
|
114
127
|
return;
|
|
115
128
|
}
|
|
116
129
|
const presented = Buffer.from(tokenPart, 'utf8');
|
|
117
130
|
if (!constantTimeEquals(presented, remoteToken)) {
|
|
118
|
-
res.status(401).json({ success: false, error: 'Invalid bearer token' });
|
|
131
|
+
res.status(401).json({ success: false, errorCode: 'auth.invalid-token', error: 'Invalid bearer token' });
|
|
119
132
|
return;
|
|
120
133
|
}
|
|
121
134
|
next();
|
|
@@ -130,6 +143,7 @@ function payloadTooLargeHandler(err, _req, res, next) {
|
|
|
130
143
|
if (e.type === 'entity.parse.failed' || (err instanceof SyntaxError && (e.status === 400 || e.statusCode === 400))) {
|
|
131
144
|
res.status(400).json({
|
|
132
145
|
success: false,
|
|
146
|
+
errorCode: 'validation.bad-body',
|
|
133
147
|
error: 'Request body is not valid JSON.',
|
|
134
148
|
hint: 'Send a JSON object with Content-Type: application/json.',
|
|
135
149
|
});
|
|
@@ -140,6 +154,7 @@ function payloadTooLargeHandler(err, _req, res, next) {
|
|
|
140
154
|
return next(err);
|
|
141
155
|
res.status(413).json({
|
|
142
156
|
success: false,
|
|
157
|
+
errorCode: 'payload.too-large',
|
|
143
158
|
error: 'Request body exceeds the 1MB limit',
|
|
144
159
|
code: 'PAYLOAD_TOO_LARGE',
|
|
145
160
|
limit: '1mb',
|
|
@@ -179,12 +194,13 @@ app.get('/v1/health', (_req, res) => {
|
|
|
179
194
|
if (message === 'Database not opened') {
|
|
180
195
|
res.status(503).json({
|
|
181
196
|
success: false,
|
|
197
|
+
errorCode: 'server.internal',
|
|
182
198
|
error: 'Database not initialized',
|
|
183
199
|
details: 'MeMesh database failed to open at startup. Check server logs for details, or run "memesh doctor" to diagnose.',
|
|
184
200
|
});
|
|
185
201
|
}
|
|
186
202
|
else {
|
|
187
|
-
res.status(500).json({ success: false, error: message });
|
|
203
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: message });
|
|
188
204
|
}
|
|
189
205
|
}
|
|
190
206
|
});
|
|
@@ -209,7 +225,7 @@ app.get('/v1/doctor', async (_req, res) => {
|
|
|
209
225
|
res.json({ success: true, data: safe });
|
|
210
226
|
}
|
|
211
227
|
catch (err) {
|
|
212
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
228
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
213
229
|
}
|
|
214
230
|
});
|
|
215
231
|
function requireJsonBody(req, res) {
|
|
@@ -217,6 +233,7 @@ function requireJsonBody(req, res) {
|
|
|
217
233
|
return true;
|
|
218
234
|
res.status(400).json({
|
|
219
235
|
success: false,
|
|
236
|
+
errorCode: 'validation.bad-body',
|
|
220
237
|
error: 'No JSON body was parsed from this request.',
|
|
221
238
|
hint: 'Send the payload with Content-Type: application/json.',
|
|
222
239
|
});
|
|
@@ -229,19 +246,20 @@ function handlePost(schema, req, res, handler) {
|
|
|
229
246
|
if (!parsed.success) {
|
|
230
247
|
res.status(400).json({
|
|
231
248
|
success: false,
|
|
249
|
+
errorCode: 'validation.bad-body',
|
|
232
250
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
233
251
|
});
|
|
234
252
|
return;
|
|
235
253
|
}
|
|
236
254
|
Promise.resolve(handler(parsed.data))
|
|
237
255
|
.then((data) => res.json({ success: true, data }))
|
|
238
|
-
.catch((err) => res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
256
|
+
.catch((err) => res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) }));
|
|
239
257
|
}
|
|
240
258
|
function handleGet(res, produce) {
|
|
241
259
|
Promise.resolve()
|
|
242
260
|
.then(produce)
|
|
243
261
|
.then((data) => res.json({ success: true, data }))
|
|
244
|
-
.catch((err) => res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
262
|
+
.catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
|
|
245
263
|
}
|
|
246
264
|
app.post('/v1/remember', (req, res) => handlePost(RememberBody, req, res, remember));
|
|
247
265
|
app.post('/v1/recall', async (req, res) => {
|
|
@@ -249,7 +267,7 @@ app.post('/v1/recall', async (req, res) => {
|
|
|
249
267
|
return;
|
|
250
268
|
const parsed = RecallBody.safeParse(req.body);
|
|
251
269
|
if (!parsed.success) {
|
|
252
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
270
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
253
271
|
return;
|
|
254
272
|
}
|
|
255
273
|
try {
|
|
@@ -257,27 +275,44 @@ app.post('/v1/recall', async (req, res) => {
|
|
|
257
275
|
res.json({ success: true, data: conflicts.length > 0 ? { entities, conflicts } : entities });
|
|
258
276
|
}
|
|
259
277
|
catch (err) {
|
|
260
|
-
res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
278
|
+
res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
|
|
261
279
|
}
|
|
262
280
|
});
|
|
263
281
|
app.post('/v1/forget', (req, res) => handlePost(ForgetBody, req, res, forget));
|
|
264
282
|
app.post('/v1/consolidate', (_req, res) => {
|
|
265
|
-
res.status(410).json({ success: false, error: RETIRED_ROUTES['/v1/consolidate'] });
|
|
283
|
+
res.status(410).json({ success: false, errorCode: 'route.retired', error: RETIRED_ROUTES['/v1/consolidate'] });
|
|
266
284
|
});
|
|
267
285
|
app.post('/v1/export', (req, res) => handlePost(ExportBody, req, res, exportMemories));
|
|
268
286
|
app.post('/v1/import', (req, res) => handlePost(ImportBody, req, res, importMemories));
|
|
269
287
|
app.post('/v1/learn', (req, res) => handlePost(LearnBody, req, res, learn));
|
|
270
288
|
app.post('/v1/verify', (req, res) => handlePost(VerifyBody, req, res, verifyAgentWork));
|
|
289
|
+
const API_KEY_MASK = '***';
|
|
271
290
|
function maskLlmSecrets(obj) {
|
|
272
291
|
const masked = { ...obj };
|
|
273
292
|
if (masked.llm?.apiKey) {
|
|
274
|
-
masked.llm = { ...masked.llm, apiKey:
|
|
293
|
+
masked.llm = { ...masked.llm, apiKey: API_KEY_MASK };
|
|
275
294
|
}
|
|
276
295
|
if (Array.isArray(masked.llmFallbacks) && masked.llmFallbacks.length > 0) {
|
|
277
|
-
masked.llmFallbacks = masked.llmFallbacks.map(fb => fb?.apiKey ? { ...fb, apiKey:
|
|
296
|
+
masked.llmFallbacks = masked.llmFallbacks.map(fb => fb?.apiKey ? { ...fb, apiKey: API_KEY_MASK } : fb);
|
|
278
297
|
}
|
|
279
298
|
return masked;
|
|
280
299
|
}
|
|
300
|
+
function preserveFallbackApiKeys(incoming, stored) {
|
|
301
|
+
return incoming.map((entry) => {
|
|
302
|
+
const { keepKeyFrom, ...clean } = entry;
|
|
303
|
+
if (clean.apiKey === API_KEY_MASK)
|
|
304
|
+
delete clean.apiKey;
|
|
305
|
+
if (clean.apiKey)
|
|
306
|
+
return clean;
|
|
307
|
+
if (typeof keepKeyFrom === 'number' && stored && keepKeyFrom >= 0 && keepKeyFrom < stored.length) {
|
|
308
|
+
const src = stored[keepKeyFrom];
|
|
309
|
+
if (src && src.provider === clean.provider && src.apiKey) {
|
|
310
|
+
return { ...clean, apiKey: src.apiKey };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return clean;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
281
316
|
app.get('/v1/config', (_req, res) => {
|
|
282
317
|
try {
|
|
283
318
|
const config = readConfig();
|
|
@@ -285,7 +320,7 @@ app.get('/v1/config', (_req, res) => {
|
|
|
285
320
|
res.json({ success: true, data: { config: maskLlmSecrets(config), capabilities: maskLlmSecrets(caps) } });
|
|
286
321
|
}
|
|
287
322
|
catch (err) {
|
|
288
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
323
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
289
324
|
}
|
|
290
325
|
});
|
|
291
326
|
const ConfigBody = z.object({
|
|
@@ -301,11 +336,17 @@ const ConfigBody = z.object({
|
|
|
301
336
|
provider: z.enum(['anthropic', 'openai', 'ollama']),
|
|
302
337
|
model: z.string().optional(),
|
|
303
338
|
apiKey: z.string().optional(),
|
|
339
|
+
keepKeyFrom: z.number().int().nonnegative().nullable().optional(),
|
|
304
340
|
})).optional(),
|
|
305
341
|
autoCapture: z.boolean().optional(),
|
|
306
342
|
sessionLimit: z.number().int().min(1).max(100).optional(),
|
|
307
343
|
enableAgenticOrchestration: z.boolean().optional(),
|
|
308
344
|
autoUpdate: z.enum(['off', 'patch', 'minor', 'major']).optional(),
|
|
345
|
+
language: z.string().trim().min(1).max(60)
|
|
346
|
+
.refine((v) => languageValueError(v) === null, {
|
|
347
|
+
message: 'language must not contain line breaks or other control characters',
|
|
348
|
+
})
|
|
349
|
+
.optional(),
|
|
309
350
|
setupCompleted: z.boolean().optional(),
|
|
310
351
|
}).strip();
|
|
311
352
|
app.post('/v1/config', async (req, res) => {
|
|
@@ -313,29 +354,34 @@ app.post('/v1/config', async (req, res) => {
|
|
|
313
354
|
return;
|
|
314
355
|
const parsed = ConfigBody.safeParse(req.body);
|
|
315
356
|
if (!parsed.success) {
|
|
316
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
357
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; ') });
|
|
317
358
|
return;
|
|
318
359
|
}
|
|
319
360
|
try {
|
|
320
361
|
const before = readConfig();
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
362
|
+
if (parsed.data.llm && parsed.data.llm.apiKey === API_KEY_MASK) {
|
|
363
|
+
if (before.llm && before.llm.provider === parsed.data.llm.provider && before.llm.apiKey) {
|
|
364
|
+
parsed.data.llm.apiKey = before.llm.apiKey;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
delete parsed.data.llm.apiKey;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (parsed.data.llmFallbacks) {
|
|
371
|
+
parsed.data.llmFallbacks = preserveFallbackApiKeys(parsed.data.llmFallbacks, before.llmFallbacks);
|
|
328
372
|
}
|
|
373
|
+
const updated = updateConfig(parsed.data);
|
|
329
374
|
res.json({ success: true, data: maskLlmSecrets(updated) });
|
|
330
375
|
}
|
|
331
376
|
catch (err) {
|
|
332
|
-
res.status(400).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
377
|
+
res.status(400).json({ success: false, errorCode: 'operation.failed', error: err instanceof Error ? err.message : String(err) });
|
|
333
378
|
}
|
|
334
379
|
});
|
|
335
380
|
const ConfigTestBody = z.object({
|
|
336
381
|
provider: z.enum(['anthropic', 'openai', 'ollama']),
|
|
337
382
|
apiKey: z.string().max(500).optional(),
|
|
338
383
|
host: z.string().max(500).optional(),
|
|
384
|
+
fallbackIndex: z.number().int().nonnegative().optional(),
|
|
339
385
|
});
|
|
340
386
|
app.post('/v1/config/test', async (req, res) => {
|
|
341
387
|
if (!requireJsonBody(req, res))
|
|
@@ -344,17 +390,23 @@ app.post('/v1/config/test', async (req, res) => {
|
|
|
344
390
|
if (!parsed.success) {
|
|
345
391
|
res.status(400).json({
|
|
346
392
|
success: false,
|
|
393
|
+
errorCode: 'validation.bad-body',
|
|
347
394
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
348
395
|
});
|
|
349
396
|
return;
|
|
350
397
|
}
|
|
351
398
|
try {
|
|
352
399
|
const { probeProvider } = await import('../../core/llm-validator.js');
|
|
353
|
-
const { provider, host } = parsed.data;
|
|
400
|
+
const { provider, host, fallbackIndex } = parsed.data;
|
|
354
401
|
let { apiKey } = parsed.data;
|
|
355
402
|
if (!apiKey && (provider === 'anthropic' || provider === 'openai')) {
|
|
356
403
|
const existing = readConfig();
|
|
357
|
-
if (
|
|
404
|
+
if (typeof fallbackIndex === 'number') {
|
|
405
|
+
const fb = existing.llmFallbacks?.[fallbackIndex];
|
|
406
|
+
if (fb && fb.provider === provider && fb.apiKey)
|
|
407
|
+
apiKey = fb.apiKey;
|
|
408
|
+
}
|
|
409
|
+
else if (existing.llm?.provider === provider && existing.llm.apiKey) {
|
|
358
410
|
apiKey = existing.llm.apiKey;
|
|
359
411
|
}
|
|
360
412
|
}
|
|
@@ -362,7 +414,7 @@ app.post('/v1/config/test', async (req, res) => {
|
|
|
362
414
|
res.json({ success: true, data: result });
|
|
363
415
|
}
|
|
364
416
|
catch (err) {
|
|
365
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
417
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
366
418
|
}
|
|
367
419
|
});
|
|
368
420
|
app.get('/v1/update-status', async (req, res) => {
|
|
@@ -396,7 +448,7 @@ app.get('/v1/update-status', async (req, res) => {
|
|
|
396
448
|
});
|
|
397
449
|
}
|
|
398
450
|
catch (err) {
|
|
399
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
451
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
400
452
|
}
|
|
401
453
|
});
|
|
402
454
|
app.get('/v1/graph', (_req, res) => handleGet(res, () => computeGraph(getDatabase())));
|
|
@@ -415,7 +467,7 @@ app.post('/v1/demo/seed', async (_req, res) => {
|
|
|
415
467
|
res.json({ success: true, data });
|
|
416
468
|
}
|
|
417
469
|
catch (err) {
|
|
418
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
470
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
419
471
|
}
|
|
420
472
|
});
|
|
421
473
|
app.post('/v1/demo/reset', async (_req, res) => {
|
|
@@ -425,7 +477,7 @@ app.post('/v1/demo/reset', async (_req, res) => {
|
|
|
425
477
|
res.json({ success: true, data });
|
|
426
478
|
}
|
|
427
479
|
catch (err) {
|
|
428
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
480
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
429
481
|
}
|
|
430
482
|
});
|
|
431
483
|
app.get('/v1/projects', (_req, res) => handleGet(res, () => computeProjects(getDatabase())));
|
|
@@ -437,7 +489,7 @@ app.get('/v1/telemetry', async (req, res) => {
|
|
|
437
489
|
try {
|
|
438
490
|
const parsed = TelemetryQuerySchema.safeParse(req.query);
|
|
439
491
|
if (!parsed.success) {
|
|
440
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
492
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
441
493
|
return;
|
|
442
494
|
}
|
|
443
495
|
const { summariseTelemetry } = await import('../../core/llm-telemetry.js');
|
|
@@ -445,7 +497,7 @@ app.get('/v1/telemetry', async (req, res) => {
|
|
|
445
497
|
res.json({ success: true, data: { window_days: parsed.data.window, summaries } });
|
|
446
498
|
}
|
|
447
499
|
catch (err) {
|
|
448
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
500
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
449
501
|
}
|
|
450
502
|
});
|
|
451
503
|
const DreamProposalsQuerySchema = z.object({
|
|
@@ -455,7 +507,7 @@ app.get('/v1/dream/proposals', (req, res) => {
|
|
|
455
507
|
try {
|
|
456
508
|
const parsed = DreamProposalsQuerySchema.safeParse(req.query);
|
|
457
509
|
if (!parsed.success) {
|
|
458
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
510
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
459
511
|
return;
|
|
460
512
|
}
|
|
461
513
|
const status = parsed.data.status;
|
|
@@ -465,22 +517,22 @@ app.get('/v1/dream/proposals', (req, res) => {
|
|
|
465
517
|
? [...listProposals(db, 'pending'), ...listProposals(db, 'applied'), ...listProposals(db, 'rejected')]
|
|
466
518
|
: listProposals(db, status);
|
|
467
519
|
res.json({ success: true, data: rows });
|
|
468
|
-
}).catch((err) => res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) }));
|
|
520
|
+
}).catch((err) => res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) }));
|
|
469
521
|
}
|
|
470
522
|
catch (err) {
|
|
471
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
523
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
472
524
|
}
|
|
473
525
|
});
|
|
474
526
|
app.get('/v1/dream/proposals/:id', (req, res) => {
|
|
475
527
|
const id = parseInt(req.params.id, 10);
|
|
476
528
|
if (!Number.isInteger(id) || id < 1) {
|
|
477
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
529
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
478
530
|
return;
|
|
479
531
|
}
|
|
480
532
|
try {
|
|
481
533
|
const row = getDatabase().prepare('SELECT id, project, cluster_key, source_ids, proposed_digest, llm_model, prompt_version, status, reason, created_at, reviewed_at FROM dream_proposals WHERE id = ?').get(id);
|
|
482
534
|
if (!row) {
|
|
483
|
-
res.status(404).json({ success: false, error: `proposal #${id} not found` });
|
|
535
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `proposal #${id} not found` });
|
|
484
536
|
return;
|
|
485
537
|
}
|
|
486
538
|
let digest = null;
|
|
@@ -496,7 +548,7 @@ app.get('/v1/dream/proposals/:id', (req, res) => {
|
|
|
496
548
|
res.json({ success: true, data: { ...row, proposed_digest: digest, source_ids: sourceIds } });
|
|
497
549
|
}
|
|
498
550
|
catch (err) {
|
|
499
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
551
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
500
552
|
}
|
|
501
553
|
});
|
|
502
554
|
const DreamRunBody = z.object({
|
|
@@ -510,6 +562,7 @@ app.post('/v1/dream/run', async (req, res) => {
|
|
|
510
562
|
if (!parsed.success) {
|
|
511
563
|
res.status(400).json({
|
|
512
564
|
success: false,
|
|
565
|
+
errorCode: 'validation.bad-body',
|
|
513
566
|
error: parsed.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join('; '),
|
|
514
567
|
});
|
|
515
568
|
return;
|
|
@@ -520,6 +573,7 @@ app.post('/v1/dream/run', async (req, res) => {
|
|
|
520
573
|
if (!cfg.llm) {
|
|
521
574
|
res.status(400).json({
|
|
522
575
|
success: false,
|
|
576
|
+
errorCode: 'llm.not-configured',
|
|
523
577
|
error: 'No LLM configured — dream run requires Smart Mode. Configure a provider in Settings.',
|
|
524
578
|
});
|
|
525
579
|
return;
|
|
@@ -534,13 +588,13 @@ app.post('/v1/dream/run', async (req, res) => {
|
|
|
534
588
|
res.json({ success: true, data: result });
|
|
535
589
|
}
|
|
536
590
|
catch (err) {
|
|
537
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
591
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
538
592
|
}
|
|
539
593
|
});
|
|
540
594
|
app.post('/v1/dream/proposals/:id/accept', async (req, res) => {
|
|
541
595
|
const id = parseInt(req.params.id, 10);
|
|
542
596
|
if (!Number.isInteger(id) || id < 1) {
|
|
543
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
597
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
544
598
|
return;
|
|
545
599
|
}
|
|
546
600
|
try {
|
|
@@ -552,10 +606,10 @@ app.post('/v1/dream/proposals/:id/accept', async (req, res) => {
|
|
|
552
606
|
catch (err) {
|
|
553
607
|
const msg = err instanceof Error ? err.message : String(err);
|
|
554
608
|
if (/not found or not pending/.test(msg)) {
|
|
555
|
-
res.status(404).json({ success: false, error: msg });
|
|
609
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
|
|
556
610
|
}
|
|
557
611
|
else {
|
|
558
|
-
res.status(500).json({ success: false, error: msg });
|
|
612
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
|
|
559
613
|
}
|
|
560
614
|
}
|
|
561
615
|
});
|
|
@@ -565,12 +619,12 @@ const RejectBodySchema = z.object({
|
|
|
565
619
|
app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
|
|
566
620
|
const id = parseInt(req.params.id, 10);
|
|
567
621
|
if (!Number.isInteger(id) || id < 1) {
|
|
568
|
-
res.status(400).json({ success: false, error: 'invalid id' });
|
|
622
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: 'invalid id' });
|
|
569
623
|
return;
|
|
570
624
|
}
|
|
571
625
|
const parsed = RejectBodySchema.safeParse(req.body ?? {});
|
|
572
626
|
if (!parsed.success) {
|
|
573
|
-
res.status(400).json({ success: false, error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
627
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-body', error: parsed.error.issues.map(i => i.message).join('; ') });
|
|
574
628
|
return;
|
|
575
629
|
}
|
|
576
630
|
try {
|
|
@@ -581,10 +635,10 @@ app.post('/v1/dream/proposals/:id/reject', async (req, res) => {
|
|
|
581
635
|
catch (err) {
|
|
582
636
|
const msg = err instanceof Error ? err.message : String(err);
|
|
583
637
|
if (/not found or not pending/.test(msg)) {
|
|
584
|
-
res.status(404).json({ success: false, error: msg });
|
|
638
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: msg });
|
|
585
639
|
}
|
|
586
640
|
else {
|
|
587
|
-
res.status(500).json({ success: false, error: msg });
|
|
641
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: msg });
|
|
588
642
|
}
|
|
589
643
|
}
|
|
590
644
|
});
|
|
@@ -597,7 +651,7 @@ app.get('/v1/entities', (req, res) => {
|
|
|
597
651
|
try {
|
|
598
652
|
const parsed = EntitiesQuerySchema.safeParse(req.query);
|
|
599
653
|
if (!parsed.success) {
|
|
600
|
-
res.status(400).json({ success: false, error: `Invalid query: ${parsed.error.message}` });
|
|
654
|
+
res.status(400).json({ success: false, errorCode: 'validation.bad-param', error: `Invalid query: ${parsed.error.message}` });
|
|
601
655
|
return;
|
|
602
656
|
}
|
|
603
657
|
const { type: typeFilter, limit, status } = parsed.data;
|
|
@@ -610,7 +664,7 @@ app.get('/v1/entities', (req, res) => {
|
|
|
610
664
|
res.json({ success: true, data: entities });
|
|
611
665
|
}
|
|
612
666
|
catch (err) {
|
|
613
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
667
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
614
668
|
}
|
|
615
669
|
});
|
|
616
670
|
app.get('/v1/entities/:name', (req, res) => {
|
|
@@ -619,13 +673,13 @@ app.get('/v1/entities/:name', (req, res) => {
|
|
|
619
673
|
const kg = new KnowledgeGraph(db);
|
|
620
674
|
const entity = kg.getEntity(req.params.name);
|
|
621
675
|
if (!entity) {
|
|
622
|
-
res.status(404).json({ success: false, error: `Entity "${req.params.name}" not found` });
|
|
676
|
+
res.status(404).json({ success: false, errorCode: 'resource.not-found', error: `Entity "${req.params.name}" not found` });
|
|
623
677
|
return;
|
|
624
678
|
}
|
|
625
679
|
res.json({ success: true, data: entity });
|
|
626
680
|
}
|
|
627
681
|
catch (err) {
|
|
628
|
-
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
682
|
+
res.status(500).json({ success: false, errorCode: 'server.internal', error: err instanceof Error ? err.message : String(err) });
|
|
629
683
|
}
|
|
630
684
|
});
|
|
631
685
|
const HOST = process.env.MEMESH_HTTP_HOST || '127.0.0.1';
|
|
@@ -645,6 +699,7 @@ function isLoopbackHost(host) {
|
|
|
645
699
|
app.use((req, res) => {
|
|
646
700
|
res.status(404).json({
|
|
647
701
|
success: false,
|
|
702
|
+
errorCode: 'route.not-found',
|
|
648
703
|
code: 'NOT_FOUND',
|
|
649
704
|
error: `No route for ${req.method} ${req.path}`,
|
|
650
705
|
});
|
|
@@ -694,6 +749,18 @@ export function startServer(host = HOST, port = PORT, opts) {
|
|
|
694
749
|
throw new Error(`Database initialization failed: ${message}`, { cause: err });
|
|
695
750
|
}
|
|
696
751
|
logCapabilities();
|
|
752
|
+
const injectedUpdateSeam = Boolean(opts?.updateCheckImpl || opts?.lastUpdateCheckImpl);
|
|
753
|
+
const updateCheckWanted = opts?.autoUpdateCheck === true || injectedUpdateSeam;
|
|
754
|
+
if (updateCheckWanted && !process.env.MEMESH_SKIP_UPDATE_CHECK) {
|
|
755
|
+
void (async () => {
|
|
756
|
+
const readLast = opts?.lastUpdateCheckImpl ?? getLastUpdateCheck;
|
|
757
|
+
const refresh = opts?.updateCheckImpl ?? checkForUpdate;
|
|
758
|
+
const cached = readLast(packageVersion);
|
|
759
|
+
if (cached && (cached.freshness === 'fresh' || cached.freshness === 'cached'))
|
|
760
|
+
return;
|
|
761
|
+
await refresh(packageVersion);
|
|
762
|
+
})().catch(() => { });
|
|
763
|
+
}
|
|
697
764
|
const server = app.listen(port, host, () => {
|
|
698
765
|
const addr = server.address();
|
|
699
766
|
if (addr && typeof addr === 'object') {
|