@graphmemory/server 1.3.0 → 1.3.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/dist/api/index.js +4 -4
- package/dist/api/rest/code.js +2 -1
- package/dist/api/rest/docs.js +2 -1
- package/dist/api/rest/embed.js +8 -1
- package/dist/api/rest/index.js +25 -7
- package/dist/api/rest/knowledge.js +4 -2
- package/dist/api/rest/skills.js +2 -1
- package/dist/api/rest/tasks.js +2 -1
- package/dist/api/rest/validation.js +41 -40
- package/dist/api/rest/websocket.js +24 -7
- package/dist/api/tools/knowledge/add-attachment.js +2 -1
- package/dist/api/tools/skills/add-attachment.js +2 -1
- package/dist/api/tools/tasks/add-attachment.js +2 -1
- package/dist/cli/index.js +7 -4
- package/dist/cli/indexer.js +38 -25
- package/dist/graphs/attachment-types.js +5 -0
- package/dist/graphs/code.js +34 -8
- package/dist/graphs/docs.js +5 -3
- package/dist/graphs/file-index.js +5 -3
- package/dist/graphs/knowledge.js +11 -4
- package/dist/graphs/skill.js +12 -5
- package/dist/graphs/task.js +12 -5
- package/dist/lib/defaults.js +78 -0
- package/dist/lib/embedder.js +11 -12
- package/dist/lib/embedding-codec.js +3 -5
- package/dist/lib/graph-persistence.js +68 -0
- package/dist/lib/jwt.js +2 -2
- package/dist/lib/mirror-watcher.js +4 -3
- package/dist/lib/multi-config.js +3 -1
- package/dist/lib/parsers/docs.js +2 -1
- package/dist/lib/parsers/languages/typescript.js +39 -17
- package/dist/lib/project-manager.js +7 -1
- package/dist/lib/search/bm25.js +5 -4
- package/dist/lib/search/code.js +2 -1
- package/dist/lib/search/docs.js +2 -1
- package/dist/lib/search/file-index.js +2 -1
- package/dist/lib/search/files.js +3 -2
- package/dist/lib/search/knowledge.js +2 -1
- package/dist/lib/search/skills.js +2 -1
- package/dist/lib/search/tasks.js +2 -1
- package/dist/ui/assets/{NoteForm-aZX9f6-3.js → NoteForm-SQ0b93i0.js} +1 -1
- package/dist/ui/assets/{SkillForm-KYa3o92l.js → SkillForm-BVsGrNPb.js} +1 -1
- package/dist/ui/assets/{TaskForm-Bl5nkybO.js → TaskForm-DgPVeiI9.js} +1 -1
- package/dist/ui/assets/{_articleId_-DjbCByxM.js → _articleId_-FqdaSeYS.js} +1 -1
- package/dist/ui/assets/{_docId_-hdCDjclV.js → _docId_-Q0Wmjtp6.js} +1 -1
- package/dist/ui/assets/{_filePath_-CpG836v4.js → _filePath_-BR0gOT_z.js} +1 -1
- package/dist/ui/assets/{_noteId_-C1enaQd1.js → _noteId_-BMWd415J.js} +1 -1
- package/dist/ui/assets/{_skillId_-hPoCet7J.js → _skillId_-CsHgildJ.js} +1 -1
- package/dist/ui/assets/{_taskId_-DSB3dLVz.js → _taskId_-xDHTfbQw.js} +1 -1
- package/dist/ui/assets/{_toolName_-3SmCfxZy.js → _toolName_-BSa2uNSu.js} +1 -1
- package/dist/ui/assets/{attachments-CEQ-2nMo.js → attachments-NSvN5_0A.js} +1 -1
- package/dist/ui/assets/{docs-CrXsRcOG.js → docs-iUK8E40J.js} +1 -1
- package/dist/ui/assets/{edit-TUIIpUMF.js → edit-BzIJy_Oo.js} +1 -1
- package/dist/ui/assets/{edit-BYiy1FZy.js → edit-Dnc067B2.js} +1 -1
- package/dist/ui/assets/{edit-hc-ZWz3y.js → edit-U_UEI361.js} +1 -1
- package/dist/ui/assets/{files-0bPg6NH9.js → files-B4svJUZh.js} +1 -1
- package/dist/ui/assets/{graph-DXGud_wF.js → graph-CcNP1ckP.js} +1 -1
- package/dist/ui/assets/{help-DJ52_fxN.js → help-BJZZtKAR.js} +1 -1
- package/dist/ui/assets/{help-CEMQqZUR.js → help-D6XKMuzk.js} +16 -4
- package/dist/ui/assets/index-CEweXD9O.js +2 -0
- package/dist/ui/assets/{knowledge-DeygeGGH.js → knowledge-CV99ToEV.js} +1 -1
- package/dist/ui/assets/{new-CpD7hOBA.js → new-BypesKiP.js} +1 -1
- package/dist/ui/assets/{new-s8c0M75X.js → new-Dcx8wlp4.js} +1 -1
- package/dist/ui/assets/{new-DHTg3Dqq.js → new-Sq3NY2oa.js} +1 -1
- package/dist/ui/assets/{prompts-BgOmdxgM.js → prompts-DbsIe3Pm.js} +1 -1
- package/dist/ui/assets/{search-EpJhdP2a.js → search-D87r7lIL.js} +1 -1
- package/dist/ui/assets/{skill-y9pizyqE.js → skill-BltAsz7M.js} +1 -1
- package/dist/ui/assets/{skills-Cga9iUZN.js → skills-Dtmg2kDA.js} +1 -1
- package/dist/ui/assets/{tasks-CobouTKV.js → tasks-BRqIwKCG.js} +1 -1
- package/dist/ui/assets/{tools-JxKH5BDF.js → tools-CM3gQ4TK.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +5 -2
- package/dist/ui/assets/index-BCZDAYZi.js +0 -2
package/dist/api/index.js
CHANGED
|
@@ -47,6 +47,7 @@ const embedder_1 = require("../lib/embedder");
|
|
|
47
47
|
const index_1 = require("../api/rest/index");
|
|
48
48
|
const websocket_1 = require("../api/rest/websocket");
|
|
49
49
|
const access_1 = require("../lib/access");
|
|
50
|
+
const defaults_1 = require("../lib/defaults");
|
|
50
51
|
const multi_config_1 = require("../lib/multi-config");
|
|
51
52
|
const docs_1 = require("../graphs/docs");
|
|
52
53
|
const code_1 = require("../graphs/code");
|
|
@@ -314,13 +315,12 @@ function createMcpServer(docGraph, codeGraph, knowledgeGraph, fileIndexGraph, ta
|
|
|
314
315
|
// ---------------------------------------------------------------------------
|
|
315
316
|
// HTTP transport (Streamable HTTP)
|
|
316
317
|
// ---------------------------------------------------------------------------
|
|
317
|
-
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB
|
|
318
318
|
async function collectBody(req) {
|
|
319
319
|
const chunks = [];
|
|
320
320
|
let size = 0;
|
|
321
321
|
for await (const chunk of req) {
|
|
322
322
|
size += chunk.length;
|
|
323
|
-
if (size > MAX_BODY_SIZE)
|
|
323
|
+
if (size > defaults_1.MAX_BODY_SIZE)
|
|
324
324
|
throw new Error('Request body too large');
|
|
325
325
|
chunks.push(chunk);
|
|
326
326
|
}
|
|
@@ -338,7 +338,7 @@ async function startHttpServer(host, port, sessionTimeoutMs, docGraph, codeGraph
|
|
|
338
338
|
process.stderr.write(`[http] Session ${sid} timed out\n`);
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
-
},
|
|
341
|
+
}, defaults_1.SESSION_SWEEP_INTERVAL_MS);
|
|
342
342
|
sweepInterval.unref();
|
|
343
343
|
const httpServer = http_1.default.createServer(async (req, res) => {
|
|
344
344
|
try {
|
|
@@ -401,7 +401,7 @@ async function startMultiProjectHttpServer(host, port, sessionTimeoutMs, project
|
|
|
401
401
|
process.stderr.write(`[http] Session ${sid} (project: ${s.projectId}) timed out\n`);
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
|
-
},
|
|
404
|
+
}, defaults_1.SESSION_SWEEP_INTERVAL_MS);
|
|
405
405
|
sweepInterval.unref();
|
|
406
406
|
// Express app handles /api/* routes
|
|
407
407
|
const restApp = (0, index_1.createRestApp)(projectManager, restOptions);
|
package/dist/api/rest/code.js
CHANGED
|
@@ -50,7 +50,8 @@ function createCodeRouter() {
|
|
|
50
50
|
const symbol = p.codeManager.getSymbol(symbolId);
|
|
51
51
|
if (!symbol)
|
|
52
52
|
return res.status(404).json({ error: 'Symbol not found' });
|
|
53
|
-
|
|
53
|
+
const { embedding: _, fileEmbedding: _fe, ...rest } = symbol;
|
|
54
|
+
res.json(rest);
|
|
54
55
|
}
|
|
55
56
|
catch (err) {
|
|
56
57
|
next(err);
|
package/dist/api/rest/docs.js
CHANGED
|
@@ -52,7 +52,8 @@ function createDocsRouter() {
|
|
|
52
52
|
const node = p.docManager.getNode(nodeId);
|
|
53
53
|
if (!node)
|
|
54
54
|
return res.status(404).json({ error: 'Node not found' });
|
|
55
|
-
|
|
55
|
+
const { embedding: _, ...rest } = node;
|
|
56
|
+
res.json(rest);
|
|
56
57
|
}
|
|
57
58
|
catch (err) {
|
|
58
59
|
next(err);
|
package/dist/api/rest/embed.js
CHANGED
|
@@ -8,6 +8,7 @@ const crypto_1 = __importDefault(require("crypto"));
|
|
|
8
8
|
const express_1 = require("express");
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const embedder_1 = require("../../lib/embedder");
|
|
11
|
+
const embedding_codec_1 = require("../../lib/embedding-codec");
|
|
11
12
|
/**
|
|
12
13
|
* Create an Express router for the embedding API.
|
|
13
14
|
* POST /api/embed — embed texts using the server's embedding model.
|
|
@@ -16,6 +17,7 @@ function createEmbedRouter(apiConfig, modelName) {
|
|
|
16
17
|
const router = (0, express_1.Router)();
|
|
17
18
|
const embedRequestSchema = zod_1.z.object({
|
|
18
19
|
texts: zod_1.z.array(zod_1.z.string().max(apiConfig.maxTextChars)).min(1).max(apiConfig.maxTexts),
|
|
20
|
+
format: zod_1.z.enum(['json', 'base64']).optional().default('json'),
|
|
19
21
|
});
|
|
20
22
|
router.post('/', async (req, res, next) => {
|
|
21
23
|
try {
|
|
@@ -34,7 +36,12 @@ function createEmbedRouter(apiConfig, modelName) {
|
|
|
34
36
|
const parsed = embedRequestSchema.parse(req.body);
|
|
35
37
|
const inputs = parsed.texts.map(text => ({ title: text, content: '' }));
|
|
36
38
|
const embeddings = await (0, embedder_1.embedBatch)(inputs, modelName);
|
|
37
|
-
|
|
39
|
+
if (parsed.format === 'base64') {
|
|
40
|
+
res.json({ embeddings: embeddings.map(e => (0, embedding_codec_1.float32ToBase64)(e)), format: 'base64' });
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
res.json({ embeddings });
|
|
44
|
+
}
|
|
38
45
|
}
|
|
39
46
|
catch (err) {
|
|
40
47
|
if (err?.name === 'ZodError') {
|
package/dist/api/rest/index.js
CHANGED
|
@@ -24,6 +24,7 @@ const graph_1 = require("../../api/rest/graph");
|
|
|
24
24
|
const tools_1 = require("../../api/rest/tools");
|
|
25
25
|
const embed_1 = require("../../api/rest/embed");
|
|
26
26
|
const team_1 = require("../../lib/team");
|
|
27
|
+
const defaults_1 = require("../../lib/defaults");
|
|
27
28
|
/**
|
|
28
29
|
* Express middleware: reject if accessLevel (set by requireGraphAccess) is not 'rw'.
|
|
29
30
|
* Use on POST/PUT/DELETE routes inside domain routers.
|
|
@@ -46,7 +47,7 @@ function createRestApp(projectManager, options) {
|
|
|
46
47
|
const users = options?.users ?? {};
|
|
47
48
|
const hasUsers = Object.keys(users).length > 0;
|
|
48
49
|
const corsOrigins = serverConfig?.corsOrigins;
|
|
49
|
-
app.use((0, cors_1.default)(corsOrigins?.length ? { origin: corsOrigins, credentials: true } : {}));
|
|
50
|
+
app.use((0, cors_1.default)(corsOrigins?.length ? { origin: corsOrigins, credentials: true } : { credentials: true }));
|
|
50
51
|
app.use(express_1.default.json({ limit: '10mb' }));
|
|
51
52
|
app.use((0, cookie_parser_1.default)());
|
|
52
53
|
// Security headers
|
|
@@ -59,10 +60,10 @@ function createRestApp(projectManager, options) {
|
|
|
59
60
|
const rl = serverConfig?.rateLimit;
|
|
60
61
|
const rateLimitMsg = { error: 'Too many requests, please try again later' };
|
|
61
62
|
if (rl && rl.global > 0) {
|
|
62
|
-
app.use('/api/', (0, express_rate_limit_1.default)({ windowMs:
|
|
63
|
+
app.use('/api/', (0, express_rate_limit_1.default)({ windowMs: defaults_1.RATE_LIMIT_WINDOW_MS, max: rl.global, standardHeaders: true, legacyHeaders: false, message: rateLimitMsg }));
|
|
63
64
|
}
|
|
64
65
|
if (rl && rl.search > 0) {
|
|
65
|
-
const searchLimiter = (0, express_rate_limit_1.default)({ windowMs:
|
|
66
|
+
const searchLimiter = (0, express_rate_limit_1.default)({ windowMs: defaults_1.RATE_LIMIT_WINDOW_MS, max: rl.search, standardHeaders: true, legacyHeaders: false, message: rateLimitMsg });
|
|
66
67
|
app.use('/api/projects/:projectId/knowledge/search', searchLimiter);
|
|
67
68
|
app.use('/api/projects/:projectId/tasks/search', searchLimiter);
|
|
68
69
|
app.use('/api/projects/:projectId/skills/search', searchLimiter);
|
|
@@ -72,9 +73,10 @@ function createRestApp(projectManager, options) {
|
|
|
72
73
|
app.use('/api/embed', searchLimiter);
|
|
73
74
|
}
|
|
74
75
|
if (rl && rl.auth > 0) {
|
|
75
|
-
app.use('/api/auth/login', (0, express_rate_limit_1.default)({ windowMs:
|
|
76
|
+
app.use('/api/auth/login', (0, express_rate_limit_1.default)({ windowMs: defaults_1.RATE_LIMIT_WINDOW_MS, max: rl.auth, standardHeaders: true, legacyHeaders: false, message: rateLimitMsg }));
|
|
76
77
|
}
|
|
77
78
|
const jwtSecret = serverConfig?.jwtSecret;
|
|
79
|
+
const cookieSecure = serverConfig?.cookieSecure;
|
|
78
80
|
const accessTokenTtl = serverConfig?.accessTokenTtl ?? '15m';
|
|
79
81
|
const refreshTokenTtl = serverConfig?.refreshTokenTtl ?? '7d';
|
|
80
82
|
// --- Auth endpoints (before auth middleware — always accessible) ---
|
|
@@ -89,7 +91,7 @@ function createRestApp(projectManager, options) {
|
|
|
89
91
|
const payload = (0, jwt_1.verifyToken)(accessToken, jwtSecret);
|
|
90
92
|
if (payload?.type === 'access' && users[payload.userId]) {
|
|
91
93
|
const user = users[payload.userId];
|
|
92
|
-
return res.json({ required: true, authenticated: true, userId: payload.userId, name: user.name
|
|
94
|
+
return res.json({ required: true, authenticated: true, userId: payload.userId, name: user.name });
|
|
93
95
|
}
|
|
94
96
|
}
|
|
95
97
|
}
|
|
@@ -102,6 +104,22 @@ function createRestApp(projectManager, options) {
|
|
|
102
104
|
}
|
|
103
105
|
return res.json({ required: true, authenticated: false });
|
|
104
106
|
});
|
|
107
|
+
// API key retrieval: requires valid JWT cookie (not exposed in /status)
|
|
108
|
+
app.get('/api/auth/apikey', (req, res) => {
|
|
109
|
+
if (!hasUsers || !jwtSecret) {
|
|
110
|
+
return res.status(400).json({ error: 'Authentication not configured' });
|
|
111
|
+
}
|
|
112
|
+
const accessToken = (0, jwt_1.getAccessToken)(req);
|
|
113
|
+
if (!accessToken) {
|
|
114
|
+
return res.status(401).json({ error: 'Authentication required' });
|
|
115
|
+
}
|
|
116
|
+
const payload = (0, jwt_1.verifyToken)(accessToken, jwtSecret);
|
|
117
|
+
if (!payload || payload.type !== 'access' || !users[payload.userId]) {
|
|
118
|
+
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
119
|
+
}
|
|
120
|
+
const user = users[payload.userId];
|
|
121
|
+
res.json({ apiKey: user.apiKey ?? null });
|
|
122
|
+
});
|
|
105
123
|
// Login: email + password → set JWT cookies
|
|
106
124
|
app.post('/api/auth/login', async (req, res) => {
|
|
107
125
|
if (!hasUsers || !jwtSecret) {
|
|
@@ -121,7 +139,7 @@ function createRestApp(projectManager, options) {
|
|
|
121
139
|
}
|
|
122
140
|
const accessToken = (0, jwt_1.signAccessToken)(result.userId, jwtSecret, accessTokenTtl);
|
|
123
141
|
const refreshToken = (0, jwt_1.signRefreshToken)(result.userId, jwtSecret, refreshTokenTtl);
|
|
124
|
-
(0, jwt_1.setAuthCookies)(res, accessToken, refreshToken, accessTokenTtl, refreshTokenTtl);
|
|
142
|
+
(0, jwt_1.setAuthCookies)(res, accessToken, refreshToken, accessTokenTtl, refreshTokenTtl, cookieSecure);
|
|
125
143
|
res.json({ userId: result.userId, name: result.user.name });
|
|
126
144
|
});
|
|
127
145
|
// Refresh: refresh cookie → new access cookie
|
|
@@ -144,7 +162,7 @@ function createRestApp(projectManager, options) {
|
|
|
144
162
|
}
|
|
145
163
|
const newAccessToken = (0, jwt_1.signAccessToken)(payload.userId, jwtSecret, accessTokenTtl);
|
|
146
164
|
const newRefreshToken = (0, jwt_1.signRefreshToken)(payload.userId, jwtSecret, refreshTokenTtl);
|
|
147
|
-
(0, jwt_1.setAuthCookies)(res, newAccessToken, newRefreshToken, accessTokenTtl, refreshTokenTtl);
|
|
165
|
+
(0, jwt_1.setAuthCookies)(res, newAccessToken, newRefreshToken, accessTokenTtl, refreshTokenTtl, cookieSecure);
|
|
148
166
|
res.json({ userId: payload.userId, name: users[payload.userId].name });
|
|
149
167
|
});
|
|
150
168
|
// Logout: clear cookies
|
|
@@ -11,7 +11,8 @@ const multer_1 = __importDefault(require("multer"));
|
|
|
11
11
|
const validation_1 = require("../../api/rest/validation");
|
|
12
12
|
const index_1 = require("../../api/rest/index");
|
|
13
13
|
const manager_types_1 = require("../../graphs/manager-types");
|
|
14
|
-
const
|
|
14
|
+
const defaults_1 = require("../../lib/defaults");
|
|
15
|
+
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: defaults_1.MAX_UPLOAD_SIZE } });
|
|
15
16
|
function createKnowledgeRouter() {
|
|
16
17
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
17
18
|
function getProject(req) {
|
|
@@ -52,7 +53,8 @@ function createKnowledgeRouter() {
|
|
|
52
53
|
const note = p.knowledgeManager.getNote(req.params.noteId);
|
|
53
54
|
if (!note)
|
|
54
55
|
return res.status(404).json({ error: 'Note not found' });
|
|
55
|
-
|
|
56
|
+
const { embedding: _, ...rest } = note;
|
|
57
|
+
res.json(rest);
|
|
56
58
|
}
|
|
57
59
|
catch (err) {
|
|
58
60
|
next(err);
|
package/dist/api/rest/skills.js
CHANGED
|
@@ -11,7 +11,8 @@ const multer_1 = __importDefault(require("multer"));
|
|
|
11
11
|
const validation_1 = require("../../api/rest/validation");
|
|
12
12
|
const index_1 = require("../../api/rest/index");
|
|
13
13
|
const manager_types_1 = require("../../graphs/manager-types");
|
|
14
|
-
const
|
|
14
|
+
const defaults_1 = require("../../lib/defaults");
|
|
15
|
+
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: defaults_1.MAX_UPLOAD_SIZE } });
|
|
15
16
|
function createSkillsRouter() {
|
|
16
17
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
17
18
|
function getProject(req) {
|
package/dist/api/rest/tasks.js
CHANGED
|
@@ -11,7 +11,8 @@ const multer_1 = __importDefault(require("multer"));
|
|
|
11
11
|
const validation_1 = require("../../api/rest/validation");
|
|
12
12
|
const index_1 = require("../../api/rest/index");
|
|
13
13
|
const manager_types_1 = require("../../graphs/manager-types");
|
|
14
|
-
const
|
|
14
|
+
const defaults_1 = require("../../lib/defaults");
|
|
15
|
+
const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: defaults_1.MAX_UPLOAD_SIZE } });
|
|
15
16
|
function createTasksRouter() {
|
|
16
17
|
const router = (0, express_1.Router)({ mergeParams: true });
|
|
17
18
|
function getProject(req) {
|
|
@@ -4,6 +4,7 @@ exports.attachmentFilenameSchema = exports.linkedQuerySchema = exports.graphExpo
|
|
|
4
4
|
exports.validateBody = validateBody;
|
|
5
5
|
exports.validateQuery = validateQuery;
|
|
6
6
|
const zod_1 = require("zod");
|
|
7
|
+
const defaults_1 = require("../../lib/defaults");
|
|
7
8
|
function validateBody(schema) {
|
|
8
9
|
return (req, _res, next) => {
|
|
9
10
|
req.body = schema.parse(req.body);
|
|
@@ -20,14 +21,14 @@ function validateQuery(schema) {
|
|
|
20
21
|
// Knowledge schemas
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
exports.createNoteSchema = zod_1.z.object({
|
|
23
|
-
title: zod_1.z.string().min(1).max(
|
|
24
|
-
content: zod_1.z.string().max(
|
|
25
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
24
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN),
|
|
25
|
+
content: zod_1.z.string().max(defaults_1.MAX_NOTE_CONTENT_LEN),
|
|
26
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional().default([]),
|
|
26
27
|
});
|
|
27
28
|
exports.updateNoteSchema = zod_1.z.object({
|
|
28
|
-
title: zod_1.z.string().min(1).max(
|
|
29
|
-
content: zod_1.z.string().max(
|
|
30
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
29
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN).optional(),
|
|
30
|
+
content: zod_1.z.string().max(defaults_1.MAX_NOTE_CONTENT_LEN).optional(),
|
|
31
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional(),
|
|
31
32
|
version: zod_1.z.number().int().positive().optional(),
|
|
32
33
|
});
|
|
33
34
|
exports.createRelationSchema = zod_1.z.object({
|
|
@@ -38,8 +39,8 @@ exports.createRelationSchema = zod_1.z.object({
|
|
|
38
39
|
projectId: zod_1.z.string().min(1).optional(),
|
|
39
40
|
});
|
|
40
41
|
exports.noteSearchSchema = zod_1.z.object({
|
|
41
|
-
q: zod_1.z.string().min(1).max(
|
|
42
|
-
topK: zod_1.z.coerce.number().int().positive().max(
|
|
42
|
+
q: zod_1.z.string().min(1).max(defaults_1.MAX_SEARCH_QUERY_LEN),
|
|
43
|
+
topK: zod_1.z.coerce.number().int().positive().max(defaults_1.MAX_SEARCH_TOP_K).optional(),
|
|
43
44
|
minScore: zod_1.z.coerce.number().min(0).max(1).optional(),
|
|
44
45
|
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional(),
|
|
45
46
|
});
|
|
@@ -52,24 +53,24 @@ exports.noteListSchema = zod_1.z.object({
|
|
|
52
53
|
// Task schemas
|
|
53
54
|
// ---------------------------------------------------------------------------
|
|
54
55
|
exports.createTaskSchema = zod_1.z.object({
|
|
55
|
-
title: zod_1.z.string().min(1).max(
|
|
56
|
-
description: zod_1.z.string().max(
|
|
56
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN),
|
|
57
|
+
description: zod_1.z.string().max(defaults_1.MAX_DESCRIPTION_LEN).default(''),
|
|
57
58
|
status: zod_1.z.enum(['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled']).default('todo'),
|
|
58
59
|
priority: zod_1.z.enum(['critical', 'high', 'medium', 'low']).default('medium'),
|
|
59
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
60
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional().default([]),
|
|
60
61
|
dueDate: zod_1.z.number().nullable().optional(),
|
|
61
62
|
estimate: zod_1.z.number().nullable().optional(),
|
|
62
|
-
assignee: zod_1.z.string().max(
|
|
63
|
+
assignee: zod_1.z.string().max(defaults_1.MAX_ASSIGNEE_LEN).nullable().optional(),
|
|
63
64
|
});
|
|
64
65
|
exports.updateTaskSchema = zod_1.z.object({
|
|
65
|
-
title: zod_1.z.string().min(1).max(
|
|
66
|
-
description: zod_1.z.string().max(
|
|
66
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN).optional(),
|
|
67
|
+
description: zod_1.z.string().max(defaults_1.MAX_DESCRIPTION_LEN).optional(),
|
|
67
68
|
status: zod_1.z.enum(['backlog', 'todo', 'in_progress', 'review', 'done', 'cancelled']).optional(),
|
|
68
69
|
priority: zod_1.z.enum(['critical', 'high', 'medium', 'low']).optional(),
|
|
69
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
70
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional(),
|
|
70
71
|
dueDate: zod_1.z.number().nullable().optional(),
|
|
71
72
|
estimate: zod_1.z.number().nullable().optional(),
|
|
72
|
-
assignee: zod_1.z.string().max(
|
|
73
|
+
assignee: zod_1.z.string().max(defaults_1.MAX_ASSIGNEE_LEN).nullable().optional(),
|
|
73
74
|
version: zod_1.z.number().int().positive().optional(),
|
|
74
75
|
});
|
|
75
76
|
exports.moveTaskSchema = zod_1.z.object({
|
|
@@ -84,8 +85,8 @@ exports.createTaskLinkSchema = zod_1.z.object({
|
|
|
84
85
|
projectId: zod_1.z.string().min(1).optional(),
|
|
85
86
|
});
|
|
86
87
|
exports.taskSearchSchema = zod_1.z.object({
|
|
87
|
-
q: zod_1.z.string().min(1).max(
|
|
88
|
-
topK: zod_1.z.coerce.number().int().positive().max(
|
|
88
|
+
q: zod_1.z.string().min(1).max(defaults_1.MAX_SEARCH_QUERY_LEN),
|
|
89
|
+
topK: zod_1.z.coerce.number().int().positive().max(defaults_1.MAX_SEARCH_TOP_K).optional(),
|
|
89
90
|
minScore: zod_1.z.coerce.number().min(0).max(1).optional(),
|
|
90
91
|
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional(),
|
|
91
92
|
});
|
|
@@ -101,8 +102,8 @@ exports.taskListSchema = zod_1.z.object({
|
|
|
101
102
|
// Search schemas (docs, code, files)
|
|
102
103
|
// ---------------------------------------------------------------------------
|
|
103
104
|
exports.searchQuerySchema = zod_1.z.object({
|
|
104
|
-
q: zod_1.z.string().min(1).max(
|
|
105
|
-
topK: zod_1.z.coerce.number().int().positive().max(
|
|
105
|
+
q: zod_1.z.string().min(1).max(defaults_1.MAX_SEARCH_QUERY_LEN),
|
|
106
|
+
topK: zod_1.z.coerce.number().int().positive().max(defaults_1.MAX_SEARCH_TOP_K).optional(),
|
|
106
107
|
minScore: zod_1.z.coerce.number().min(0).max(1).optional(),
|
|
107
108
|
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional(),
|
|
108
109
|
});
|
|
@@ -127,24 +128,24 @@ exports.fileListSchema = zod_1.z.object({
|
|
|
127
128
|
// Skill schemas
|
|
128
129
|
// ---------------------------------------------------------------------------
|
|
129
130
|
exports.createSkillSchema = zod_1.z.object({
|
|
130
|
-
title: zod_1.z.string().min(1).max(
|
|
131
|
-
description: zod_1.z.string().max(
|
|
132
|
-
steps: zod_1.z.array(zod_1.z.string().max(
|
|
133
|
-
triggers: zod_1.z.array(zod_1.z.string().max(
|
|
134
|
-
inputHints: zod_1.z.array(zod_1.z.string().max(
|
|
135
|
-
filePatterns: zod_1.z.array(zod_1.z.string().max(
|
|
136
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
131
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN),
|
|
132
|
+
description: zod_1.z.string().max(defaults_1.MAX_DESCRIPTION_LEN).default(''),
|
|
133
|
+
steps: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_STEP_LEN)).max(defaults_1.MAX_SKILL_STEPS_COUNT).optional().default([]),
|
|
134
|
+
triggers: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional().default([]),
|
|
135
|
+
inputHints: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional().default([]),
|
|
136
|
+
filePatterns: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional().default([]),
|
|
137
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional().default([]),
|
|
137
138
|
source: zod_1.z.enum(['user', 'learned']).default('user'),
|
|
138
139
|
confidence: zod_1.z.number().min(0).max(1).default(1),
|
|
139
140
|
});
|
|
140
141
|
exports.updateSkillSchema = zod_1.z.object({
|
|
141
|
-
title: zod_1.z.string().min(1).max(
|
|
142
|
-
description: zod_1.z.string().max(
|
|
143
|
-
steps: zod_1.z.array(zod_1.z.string().max(
|
|
144
|
-
triggers: zod_1.z.array(zod_1.z.string().max(
|
|
145
|
-
inputHints: zod_1.z.array(zod_1.z.string().max(
|
|
146
|
-
filePatterns: zod_1.z.array(zod_1.z.string().max(
|
|
147
|
-
tags: zod_1.z.array(zod_1.z.string().max(
|
|
142
|
+
title: zod_1.z.string().min(1).max(defaults_1.MAX_TITLE_LEN).optional(),
|
|
143
|
+
description: zod_1.z.string().max(defaults_1.MAX_DESCRIPTION_LEN).optional(),
|
|
144
|
+
steps: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_STEP_LEN)).max(defaults_1.MAX_SKILL_STEPS_COUNT).optional(),
|
|
145
|
+
triggers: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional(),
|
|
146
|
+
inputHints: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional(),
|
|
147
|
+
filePatterns: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_SKILL_TRIGGER_LEN)).max(defaults_1.MAX_SKILL_TRIGGERS_COUNT).optional(),
|
|
148
|
+
tags: zod_1.z.array(zod_1.z.string().max(defaults_1.MAX_TAG_LEN)).max(defaults_1.MAX_TAGS_COUNT).optional(),
|
|
148
149
|
source: zod_1.z.enum(['user', 'learned']).optional(),
|
|
149
150
|
confidence: zod_1.z.number().min(0).max(1).optional(),
|
|
150
151
|
version: zod_1.z.number().int().positive().optional(),
|
|
@@ -157,8 +158,8 @@ exports.createSkillLinkSchema = zod_1.z.object({
|
|
|
157
158
|
projectId: zod_1.z.string().min(1).optional(),
|
|
158
159
|
});
|
|
159
160
|
exports.skillSearchSchema = zod_1.z.object({
|
|
160
|
-
q: zod_1.z.string().min(1).max(
|
|
161
|
-
topK: zod_1.z.coerce.number().int().positive().max(
|
|
161
|
+
q: zod_1.z.string().min(1).max(defaults_1.MAX_SEARCH_QUERY_LEN),
|
|
162
|
+
topK: zod_1.z.coerce.number().int().positive().max(defaults_1.MAX_SEARCH_TOP_K).optional(),
|
|
162
163
|
minScore: zod_1.z.coerce.number().min(0).max(1).optional(),
|
|
163
164
|
searchMode: zod_1.z.enum(['hybrid', 'vector', 'keyword']).optional(),
|
|
164
165
|
});
|
|
@@ -179,9 +180,9 @@ exports.graphExportSchema = zod_1.z.object({
|
|
|
179
180
|
// ---------------------------------------------------------------------------
|
|
180
181
|
exports.linkedQuerySchema = zod_1.z.object({
|
|
181
182
|
targetGraph: zod_1.z.enum(['docs', 'code', 'files', 'knowledge', 'tasks', 'skills']),
|
|
182
|
-
targetNodeId: zod_1.z.string().min(1).max(
|
|
183
|
-
kind: zod_1.z.string().max(
|
|
184
|
-
projectId: zod_1.z.string().max(
|
|
183
|
+
targetNodeId: zod_1.z.string().min(1).max(defaults_1.MAX_TARGET_NODE_ID_LEN),
|
|
184
|
+
kind: zod_1.z.string().max(defaults_1.MAX_LINK_KIND_LEN).optional(),
|
|
185
|
+
projectId: zod_1.z.string().max(defaults_1.MAX_PROJECT_ID_LEN).optional(),
|
|
185
186
|
});
|
|
186
187
|
// ---------------------------------------------------------------------------
|
|
187
188
|
// Attachment schemas
|
|
@@ -189,7 +190,7 @@ exports.linkedQuerySchema = zod_1.z.object({
|
|
|
189
190
|
/** Validates an attachment filename (path param). No path separators, no .., no dangerous chars. */
|
|
190
191
|
exports.attachmentFilenameSchema = zod_1.z.string()
|
|
191
192
|
.min(1)
|
|
192
|
-
.max(
|
|
193
|
+
.max(defaults_1.MAX_ATTACHMENT_FILENAME_LEN)
|
|
193
194
|
.refine(s => !/[/\\]/.test(s), 'Filename must not contain path separators')
|
|
194
195
|
.refine(s => !s.includes('..'), 'Filename must not contain ..')
|
|
195
196
|
.refine(s => !s.includes('\0'), 'Filename must not contain null bytes')
|
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.attachWebSocket = attachWebSocket;
|
|
4
4
|
const ws_1 = require("ws");
|
|
5
5
|
const jwt_1 = require("../../lib/jwt");
|
|
6
|
+
const defaults_1 = require("../../lib/defaults");
|
|
6
7
|
/**
|
|
7
8
|
* Attach a WebSocket server to the HTTP server at /api/ws.
|
|
8
9
|
* Broadcasts all ProjectManager events to connected clients.
|
|
9
10
|
* Each event includes projectId — clients filter on their side.
|
|
11
|
+
* Returns a handle with a cleanup function to remove all listeners.
|
|
10
12
|
*/
|
|
11
13
|
function attachWebSocket(httpServer, projectManager, options) {
|
|
12
14
|
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
@@ -55,7 +57,8 @@ function attachWebSocket(httpServer, projectManager, options) {
|
|
|
55
57
|
}
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
// Subscribe to ProjectManager events
|
|
60
|
+
// Subscribe to ProjectManager events (track handlers for cleanup)
|
|
61
|
+
const listeners = [];
|
|
59
62
|
const events = [
|
|
60
63
|
'note:created', 'note:updated', 'note:deleted',
|
|
61
64
|
'note:attachment:added', 'note:attachment:deleted',
|
|
@@ -66,14 +69,16 @@ function attachWebSocket(httpServer, projectManager, options) {
|
|
|
66
69
|
'project:indexed',
|
|
67
70
|
];
|
|
68
71
|
for (const eventType of events) {
|
|
69
|
-
|
|
72
|
+
const handler = (data) => {
|
|
70
73
|
broadcast({ projectId: data.projectId, type: eventType, data });
|
|
71
|
-
}
|
|
74
|
+
};
|
|
75
|
+
projectManager.on(eventType, handler);
|
|
76
|
+
listeners.push([eventType, handler]);
|
|
72
77
|
}
|
|
73
78
|
// Debounced graph:updated from indexer
|
|
74
79
|
let graphUpdateTimer;
|
|
75
80
|
let pendingGraphUpdates = new Map();
|
|
76
|
-
|
|
81
|
+
const graphHandler = (data) => {
|
|
77
82
|
const key = data.projectId;
|
|
78
83
|
if (!pendingGraphUpdates.has(key))
|
|
79
84
|
pendingGraphUpdates.set(key, []);
|
|
@@ -85,8 +90,20 @@ function attachWebSocket(httpServer, projectManager, options) {
|
|
|
85
90
|
}
|
|
86
91
|
pendingGraphUpdates = new Map();
|
|
87
92
|
graphUpdateTimer = undefined;
|
|
88
|
-
},
|
|
93
|
+
}, defaults_1.WS_DEBOUNCE_MS);
|
|
89
94
|
}
|
|
90
|
-
}
|
|
91
|
-
|
|
95
|
+
};
|
|
96
|
+
projectManager.on('graph:updated', graphHandler);
|
|
97
|
+
listeners.push(['graph:updated', graphHandler]);
|
|
98
|
+
function cleanup() {
|
|
99
|
+
for (const [event, handler] of listeners) {
|
|
100
|
+
projectManager.removeListener(event, handler);
|
|
101
|
+
}
|
|
102
|
+
if (graphUpdateTimer) {
|
|
103
|
+
clearTimeout(graphUpdateTimer);
|
|
104
|
+
graphUpdateTimer = undefined;
|
|
105
|
+
}
|
|
106
|
+
pendingGraphUpdates.clear();
|
|
107
|
+
}
|
|
108
|
+
return { wss, cleanup };
|
|
92
109
|
}
|
|
@@ -7,6 +7,7 @@ exports.register = register;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
|
+
const defaults_1 = require("../../../lib/defaults");
|
|
10
11
|
function register(server, mgr) {
|
|
11
12
|
server.registerTool('add_note_attachment', {
|
|
12
13
|
description: 'Attach a file to a note. Provide the absolute path to a local file. ' +
|
|
@@ -28,7 +29,7 @@ function register(server, mgr) {
|
|
|
28
29
|
if (!stat.isFile()) {
|
|
29
30
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Path is not a regular file' }) }], isError: true };
|
|
30
31
|
}
|
|
31
|
-
if (stat.size >
|
|
32
|
+
if (stat.size > defaults_1.MAX_UPLOAD_SIZE) {
|
|
32
33
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'File exceeds 50 MB limit' }) }], isError: true };
|
|
33
34
|
}
|
|
34
35
|
const data = fs_1.default.readFileSync(resolved);
|
|
@@ -7,6 +7,7 @@ exports.register = register;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
|
+
const defaults_1 = require("../../../lib/defaults");
|
|
10
11
|
function register(server, mgr) {
|
|
11
12
|
server.registerTool('add_skill_attachment', {
|
|
12
13
|
description: 'Attach a file to a skill. Provide the absolute path to a local file. ' +
|
|
@@ -28,7 +29,7 @@ function register(server, mgr) {
|
|
|
28
29
|
if (!stat.isFile()) {
|
|
29
30
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Path is not a regular file' }) }], isError: true };
|
|
30
31
|
}
|
|
31
|
-
if (stat.size >
|
|
32
|
+
if (stat.size > defaults_1.MAX_UPLOAD_SIZE) {
|
|
32
33
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'File exceeds 50 MB limit' }) }], isError: true };
|
|
33
34
|
}
|
|
34
35
|
const data = fs_1.default.readFileSync(resolved);
|
|
@@ -7,6 +7,7 @@ exports.register = register;
|
|
|
7
7
|
const fs_1 = __importDefault(require("fs"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
|
+
const defaults_1 = require("../../../lib/defaults");
|
|
10
11
|
function register(server, mgr) {
|
|
11
12
|
server.registerTool('add_task_attachment', {
|
|
12
13
|
description: 'Attach a file to a task. Provide the absolute path to a local file. ' +
|
|
@@ -28,7 +29,7 @@ function register(server, mgr) {
|
|
|
28
29
|
if (!stat.isFile()) {
|
|
29
30
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'Path is not a regular file' }) }], isError: true };
|
|
30
31
|
}
|
|
31
|
-
if (stat.size >
|
|
32
|
+
if (stat.size > defaults_1.MAX_UPLOAD_SIZE) {
|
|
32
33
|
return { content: [{ type: 'text', text: JSON.stringify({ error: 'File exceeds 50 MB limit' }) }], isError: true };
|
|
33
34
|
}
|
|
34
35
|
const data = fs_1.default.readFileSync(resolved);
|
package/dist/cli/index.js
CHANGED
|
@@ -14,11 +14,14 @@ const jwt_1 = require("../lib/jwt");
|
|
|
14
14
|
const project_manager_1 = require("../lib/project-manager");
|
|
15
15
|
const embedder_1 = require("../lib/embedder");
|
|
16
16
|
const index_1 = require("../api/index");
|
|
17
|
+
const defaults_1 = require("../lib/defaults");
|
|
17
18
|
const program = new commander_1.Command();
|
|
19
|
+
const pkgJsonPath = path_1.default.resolve(__dirname, '../../package.json');
|
|
20
|
+
const pkgVersion = JSON.parse(fs_1.default.readFileSync(pkgJsonPath, 'utf-8')).version;
|
|
18
21
|
program
|
|
19
22
|
.name('graphmemory')
|
|
20
23
|
.description('MCP server for semantic graph memory from markdown docs and source code')
|
|
21
|
-
.version(
|
|
24
|
+
.version(pkgVersion);
|
|
22
25
|
const parseIntArg = (v) => parseInt(v, 10);
|
|
23
26
|
// ---------------------------------------------------------------------------
|
|
24
27
|
// Helper: load config from file, or fall back to default (cwd as single project)
|
|
@@ -211,7 +214,7 @@ program
|
|
|
211
214
|
const forceTimer = setTimeout(() => {
|
|
212
215
|
process.stderr.write('[serve] Shutdown timeout, force exit\n');
|
|
213
216
|
process.exit(1);
|
|
214
|
-
},
|
|
217
|
+
}, defaults_1.GRACEFUL_SHUTDOWN_TIMEOUT_MS);
|
|
215
218
|
try {
|
|
216
219
|
httpServer.close();
|
|
217
220
|
// Destroy all open connections (including WebSocket) so the server can close
|
|
@@ -324,8 +327,8 @@ usersCmd
|
|
|
324
327
|
process.stderr.write('Invalid email format\n');
|
|
325
328
|
process.exit(1);
|
|
326
329
|
}
|
|
327
|
-
if (password.length >
|
|
328
|
-
process.stderr.write(
|
|
330
|
+
if (password.length > defaults_1.MAX_PASSWORD_LEN) {
|
|
331
|
+
process.stderr.write(`Password too long (max ${defaults_1.MAX_PASSWORD_LEN})\n`);
|
|
329
332
|
process.exit(1);
|
|
330
333
|
}
|
|
331
334
|
const pwHash = await (0, jwt_1.hashPassword)(password);
|