@graphmemory/server 1.1.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/LICENSE +15 -0
- package/README.md +216 -0
- package/dist/api/index.js +473 -0
- package/dist/api/rest/code.js +78 -0
- package/dist/api/rest/docs.js +80 -0
- package/dist/api/rest/embed.js +47 -0
- package/dist/api/rest/files.js +64 -0
- package/dist/api/rest/graph.js +71 -0
- package/dist/api/rest/index.js +371 -0
- package/dist/api/rest/knowledge.js +239 -0
- package/dist/api/rest/skills.js +285 -0
- package/dist/api/rest/tasks.js +273 -0
- package/dist/api/rest/tools.js +157 -0
- package/dist/api/rest/validation.js +196 -0
- package/dist/api/rest/websocket.js +71 -0
- package/dist/api/tools/code/get-file-symbols.js +30 -0
- package/dist/api/tools/code/get-symbol.js +22 -0
- package/dist/api/tools/code/list-files.js +18 -0
- package/dist/api/tools/code/search-code.js +27 -0
- package/dist/api/tools/code/search-files.js +22 -0
- package/dist/api/tools/context/get-context.js +19 -0
- package/dist/api/tools/docs/cross-references.js +76 -0
- package/dist/api/tools/docs/explain-symbol.js +55 -0
- package/dist/api/tools/docs/find-examples.js +52 -0
- package/dist/api/tools/docs/get-node.js +24 -0
- package/dist/api/tools/docs/get-toc.js +22 -0
- package/dist/api/tools/docs/list-snippets.js +46 -0
- package/dist/api/tools/docs/list-topics.js +18 -0
- package/dist/api/tools/docs/search-files.js +22 -0
- package/dist/api/tools/docs/search-snippets.js +43 -0
- package/dist/api/tools/docs/search.js +27 -0
- package/dist/api/tools/file-index/get-file-info.js +21 -0
- package/dist/api/tools/file-index/list-all-files.js +28 -0
- package/dist/api/tools/file-index/search-all-files.js +24 -0
- package/dist/api/tools/knowledge/add-attachment.js +31 -0
- package/dist/api/tools/knowledge/create-note.js +20 -0
- package/dist/api/tools/knowledge/create-relation.js +29 -0
- package/dist/api/tools/knowledge/delete-note.js +19 -0
- package/dist/api/tools/knowledge/delete-relation.js +23 -0
- package/dist/api/tools/knowledge/find-linked-notes.js +25 -0
- package/dist/api/tools/knowledge/get-note.js +20 -0
- package/dist/api/tools/knowledge/list-notes.js +18 -0
- package/dist/api/tools/knowledge/list-relations.js +17 -0
- package/dist/api/tools/knowledge/remove-attachment.js +19 -0
- package/dist/api/tools/knowledge/search-notes.js +25 -0
- package/dist/api/tools/knowledge/update-note.js +34 -0
- package/dist/api/tools/skills/add-attachment.js +31 -0
- package/dist/api/tools/skills/bump-usage.js +19 -0
- package/dist/api/tools/skills/create-skill-link.js +25 -0
- package/dist/api/tools/skills/create-skill.js +26 -0
- package/dist/api/tools/skills/delete-skill-link.js +23 -0
- package/dist/api/tools/skills/delete-skill.js +20 -0
- package/dist/api/tools/skills/find-linked-skills.js +25 -0
- package/dist/api/tools/skills/get-skill.js +21 -0
- package/dist/api/tools/skills/link-skill.js +23 -0
- package/dist/api/tools/skills/list-skills.js +20 -0
- package/dist/api/tools/skills/recall-skills.js +18 -0
- package/dist/api/tools/skills/remove-attachment.js +19 -0
- package/dist/api/tools/skills/search-skills.js +25 -0
- package/dist/api/tools/skills/update-skill.js +58 -0
- package/dist/api/tools/tasks/add-attachment.js +31 -0
- package/dist/api/tools/tasks/create-task-link.js +25 -0
- package/dist/api/tools/tasks/create-task.js +26 -0
- package/dist/api/tools/tasks/delete-task-link.js +23 -0
- package/dist/api/tools/tasks/delete-task.js +20 -0
- package/dist/api/tools/tasks/find-linked-tasks.js +25 -0
- package/dist/api/tools/tasks/get-task.js +20 -0
- package/dist/api/tools/tasks/link-task.js +23 -0
- package/dist/api/tools/tasks/list-tasks.js +25 -0
- package/dist/api/tools/tasks/move-task.js +38 -0
- package/dist/api/tools/tasks/remove-attachment.js +19 -0
- package/dist/api/tools/tasks/search-tasks.js +25 -0
- package/dist/api/tools/tasks/update-task.js +58 -0
- package/dist/cli/index.js +617 -0
- package/dist/cli/indexer.js +275 -0
- package/dist/graphs/attachment-types.js +74 -0
- package/dist/graphs/code-types.js +10 -0
- package/dist/graphs/code.js +204 -0
- package/dist/graphs/docs.js +231 -0
- package/dist/graphs/file-index-types.js +10 -0
- package/dist/graphs/file-index.js +310 -0
- package/dist/graphs/file-lang.js +119 -0
- package/dist/graphs/knowledge-types.js +32 -0
- package/dist/graphs/knowledge.js +768 -0
- package/dist/graphs/manager-types.js +87 -0
- package/dist/graphs/skill-types.js +10 -0
- package/dist/graphs/skill.js +1016 -0
- package/dist/graphs/task-types.js +17 -0
- package/dist/graphs/task.js +972 -0
- package/dist/lib/access.js +67 -0
- package/dist/lib/embedder.js +235 -0
- package/dist/lib/events-log.js +401 -0
- package/dist/lib/file-import.js +328 -0
- package/dist/lib/file-mirror.js +461 -0
- package/dist/lib/frontmatter.js +17 -0
- package/dist/lib/jwt.js +146 -0
- package/dist/lib/mirror-watcher.js +637 -0
- package/dist/lib/multi-config.js +393 -0
- package/dist/lib/parsers/code.js +214 -0
- package/dist/lib/parsers/codeblock.js +33 -0
- package/dist/lib/parsers/docs.js +199 -0
- package/dist/lib/parsers/languages/index.js +15 -0
- package/dist/lib/parsers/languages/registry.js +68 -0
- package/dist/lib/parsers/languages/types.js +2 -0
- package/dist/lib/parsers/languages/typescript.js +306 -0
- package/dist/lib/project-manager.js +458 -0
- package/dist/lib/promise-queue.js +22 -0
- package/dist/lib/search/bm25.js +167 -0
- package/dist/lib/search/code.js +103 -0
- package/dist/lib/search/docs.js +106 -0
- package/dist/lib/search/file-index.js +31 -0
- package/dist/lib/search/files.js +61 -0
- package/dist/lib/search/knowledge.js +101 -0
- package/dist/lib/search/skills.js +104 -0
- package/dist/lib/search/tasks.js +103 -0
- package/dist/lib/team.js +89 -0
- package/dist/lib/watcher.js +67 -0
- package/dist/ui/assets/index-D6oxrVF7.js +1759 -0
- package/dist/ui/assets/index-kKd4mVrh.css +1 -0
- package/dist/ui/favicon.svg +1 -0
- package/dist/ui/icons.svg +24 -0
- package/dist/ui/index.html +14 -0
- package/package.json +89 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveAccess = resolveAccess;
|
|
7
|
+
exports.resolveUserFromApiKey = resolveUserFromApiKey;
|
|
8
|
+
exports.canRead = canRead;
|
|
9
|
+
exports.canWrite = canWrite;
|
|
10
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
11
|
+
/**
|
|
12
|
+
* Resolve access level for a user on a specific graph.
|
|
13
|
+
*
|
|
14
|
+
* Resolution chain (first match wins):
|
|
15
|
+
* graph.access[userId] → project.access[userId] → workspace.access[userId]
|
|
16
|
+
* → server.access[userId] → server.defaultAccess
|
|
17
|
+
*/
|
|
18
|
+
function resolveAccess(userId, graphName, projectConfig, serverConfig, workspaceConfig) {
|
|
19
|
+
if (!userId)
|
|
20
|
+
return serverConfig.defaultAccess;
|
|
21
|
+
// 1. Graph-level
|
|
22
|
+
const graphAccess = projectConfig.graphConfigs[graphName].access?.[userId];
|
|
23
|
+
if (graphAccess)
|
|
24
|
+
return graphAccess;
|
|
25
|
+
// 2. Project-level
|
|
26
|
+
const projectAccess = projectConfig.access?.[userId];
|
|
27
|
+
if (projectAccess)
|
|
28
|
+
return projectAccess;
|
|
29
|
+
// 3. Workspace-level
|
|
30
|
+
if (workspaceConfig) {
|
|
31
|
+
const wsAccess = workspaceConfig.access?.[userId];
|
|
32
|
+
if (wsAccess)
|
|
33
|
+
return wsAccess;
|
|
34
|
+
}
|
|
35
|
+
// 4. Server-level
|
|
36
|
+
const serverAccess = serverConfig.access?.[userId];
|
|
37
|
+
if (serverAccess)
|
|
38
|
+
return serverAccess;
|
|
39
|
+
// 5. Default
|
|
40
|
+
return serverConfig.defaultAccess;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Look up a user by API key.
|
|
44
|
+
* Returns { userId, user } or undefined if not found.
|
|
45
|
+
*/
|
|
46
|
+
function resolveUserFromApiKey(apiKey, users) {
|
|
47
|
+
const keyBuf = Buffer.from(apiKey);
|
|
48
|
+
for (const [userId, user] of Object.entries(users)) {
|
|
49
|
+
const userKeyBuf = Buffer.from(user.apiKey);
|
|
50
|
+
if (keyBuf.length === userKeyBuf.length && crypto_1.default.timingSafeEqual(keyBuf, userKeyBuf)) {
|
|
51
|
+
return { userId, user };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check if an access level allows read operations.
|
|
58
|
+
*/
|
|
59
|
+
function canRead(level) {
|
|
60
|
+
return level === 'r' || level === 'rw';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if an access level allows write operations.
|
|
64
|
+
*/
|
|
65
|
+
function canWrite(level) {
|
|
66
|
+
return level === 'rw';
|
|
67
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadModel = loadModel;
|
|
7
|
+
exports.embed = embed;
|
|
8
|
+
exports.embedQuery = embedQuery;
|
|
9
|
+
exports.embedBatch = embedBatch;
|
|
10
|
+
exports.resetEmbedder = resetEmbedder;
|
|
11
|
+
exports.cosineSimilarity = cosineSimilarity;
|
|
12
|
+
const transformers_1 = require("@huggingface/transformers");
|
|
13
|
+
const fs_1 = __importDefault(require("fs"));
|
|
14
|
+
const path_1 = __importDefault(require("path"));
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// LRU cache for embedding vectors (avoids re-computing identical texts)
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
const DEFAULT_CACHE_SIZE = 10_000;
|
|
19
|
+
class LruCache {
|
|
20
|
+
maxSize;
|
|
21
|
+
map = new Map();
|
|
22
|
+
constructor(maxSize) {
|
|
23
|
+
this.maxSize = maxSize;
|
|
24
|
+
}
|
|
25
|
+
get(key) {
|
|
26
|
+
const v = this.map.get(key);
|
|
27
|
+
if (v !== undefined) {
|
|
28
|
+
// Move to end (most recently used)
|
|
29
|
+
this.map.delete(key);
|
|
30
|
+
this.map.set(key, v);
|
|
31
|
+
}
|
|
32
|
+
return v;
|
|
33
|
+
}
|
|
34
|
+
set(key, value) {
|
|
35
|
+
if (this.map.has(key))
|
|
36
|
+
this.map.delete(key);
|
|
37
|
+
this.map.set(key, value);
|
|
38
|
+
if (this.map.size > this.maxSize) {
|
|
39
|
+
// Evict oldest (first key)
|
|
40
|
+
const first = this.map.keys().next().value;
|
|
41
|
+
this.map.delete(first);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
clear() { this.map.clear(); }
|
|
45
|
+
}
|
|
46
|
+
const _models = new Map(); // name → { pipe, model, embedding }
|
|
47
|
+
const _pipeCache = new Map(); // "model|dtype" → pipe (dedup)
|
|
48
|
+
function validateRemoteUrl(url) {
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = new URL(url);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Invalid remote embedding URL: ${url}`);
|
|
55
|
+
}
|
|
56
|
+
if (!['http:', 'https:'].includes(parsed.protocol)) {
|
|
57
|
+
throw new Error(`Remote embedding URL must use http or https: ${url}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function loadModel(model, embedding, modelsDir, name = 'default') {
|
|
61
|
+
const maxChars = embedding.maxChars;
|
|
62
|
+
const cacheSize = embedding.cacheSize ?? DEFAULT_CACHE_SIZE;
|
|
63
|
+
// Remote embedding: register proxy, skip ONNX loading
|
|
64
|
+
if (embedding.remote) {
|
|
65
|
+
validateRemoteUrl(embedding.remote);
|
|
66
|
+
_models.set(name, { pipe: null, model, embedding, maxChars, cache: new LruCache(cacheSize), remote: { url: embedding.remote, apiKey: embedding.remoteApiKey } });
|
|
67
|
+
process.stderr.write(`[embedder] Model "${name}" using remote endpoint ${embedding.remote}\n`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Cache key includes dtype since same model with different dtype = different pipeline
|
|
71
|
+
const cacheKey = `${model.name}|${model.dtype ?? ''}`;
|
|
72
|
+
// Reuse pipeline if same model+dtype already loaded
|
|
73
|
+
const cached = _pipeCache.get(cacheKey);
|
|
74
|
+
if (cached) {
|
|
75
|
+
_models.set(name, { pipe: cached, model, embedding, maxChars, cache: new LruCache(cacheSize) });
|
|
76
|
+
process.stderr.write(`[embedder] Reusing model ${model.name} for "${name}"\n`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
transformers_1.env.cacheDir = modelsDir;
|
|
80
|
+
const modelDir = path_1.default.join(modelsDir, model.name.replace('/', path_1.default.sep));
|
|
81
|
+
if (fs_1.default.existsSync(modelDir)) {
|
|
82
|
+
transformers_1.env.allowRemoteModels = false;
|
|
83
|
+
process.stderr.write(`[embedder] Using local model at ${modelDir}\n`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
transformers_1.env.allowRemoteModels = true;
|
|
87
|
+
process.stderr.write(`[embedder] Downloading model ${model.name} to ${modelsDir}...\n`);
|
|
88
|
+
}
|
|
89
|
+
const pipeOpts = {};
|
|
90
|
+
if (model.dtype)
|
|
91
|
+
pipeOpts.dtype = model.dtype;
|
|
92
|
+
const pipe = await (0, transformers_1.pipeline)('feature-extraction', model.name, pipeOpts);
|
|
93
|
+
_pipeCache.set(cacheKey, pipe);
|
|
94
|
+
_models.set(name, { pipe, model, embedding, maxChars, cache: new LruCache(cacheSize) });
|
|
95
|
+
process.stderr.write(`[embedder] Model "${name}" ready\n`);
|
|
96
|
+
}
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
// Remote embedding HTTP client
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
const REMOTE_MAX_RETRIES = 3;
|
|
101
|
+
const REMOTE_BASE_DELAY_MS = 200;
|
|
102
|
+
async function remoteEmbed(url, texts, apiKey) {
|
|
103
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
104
|
+
if (apiKey)
|
|
105
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
106
|
+
const body = JSON.stringify({ texts });
|
|
107
|
+
for (let attempt = 0; attempt < REMOTE_MAX_RETRIES; attempt++) {
|
|
108
|
+
let resp;
|
|
109
|
+
try {
|
|
110
|
+
resp = await fetch(url, { method: 'POST', headers, body });
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
// Network error — retry
|
|
114
|
+
if (attempt < REMOTE_MAX_RETRIES - 1) {
|
|
115
|
+
const delay = REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
116
|
+
await new Promise(r => setTimeout(r, delay));
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`Remote embed network error after ${REMOTE_MAX_RETRIES} attempts: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
if (resp.ok) {
|
|
122
|
+
const data = await resp.json();
|
|
123
|
+
return data.embeddings;
|
|
124
|
+
}
|
|
125
|
+
// Client errors (4xx) — don't retry
|
|
126
|
+
if (resp.status < 500) {
|
|
127
|
+
const respBody = (await resp.text()).slice(0, 500);
|
|
128
|
+
throw new Error(`Remote embed failed (${resp.status}): ${respBody}`);
|
|
129
|
+
}
|
|
130
|
+
// Server errors (5xx) — retry
|
|
131
|
+
if (attempt < REMOTE_MAX_RETRIES - 1) {
|
|
132
|
+
const delay = REMOTE_BASE_DELAY_MS * 2 ** attempt;
|
|
133
|
+
await new Promise(r => setTimeout(r, delay));
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const respBody = (await resp.text()).slice(0, 500);
|
|
137
|
+
throw new Error(`Remote embed failed after ${REMOTE_MAX_RETRIES} attempts (${resp.status}): ${respBody}`);
|
|
138
|
+
}
|
|
139
|
+
throw new Error('Remote embed: unreachable');
|
|
140
|
+
}
|
|
141
|
+
function getEntry(modelName) {
|
|
142
|
+
const entry = _models.get(modelName);
|
|
143
|
+
if (!entry)
|
|
144
|
+
throw new Error(`Model "${modelName}" not loaded. Call loadModel() first.`);
|
|
145
|
+
return entry;
|
|
146
|
+
}
|
|
147
|
+
/** Embed a document (indexing). Applies documentPrefix and configured pooling. */
|
|
148
|
+
async function embed(title, content, modelName = 'default') {
|
|
149
|
+
const entry = getEntry(modelName);
|
|
150
|
+
const raw = `${title}\n${content}`;
|
|
151
|
+
const text = `${entry.model.documentPrefix}${raw}`.slice(0, entry.maxChars);
|
|
152
|
+
const cached = entry.cache.get(text);
|
|
153
|
+
if (cached)
|
|
154
|
+
return cached;
|
|
155
|
+
let vec;
|
|
156
|
+
if (entry.remote) {
|
|
157
|
+
[vec] = await remoteEmbed(entry.remote.url, [text], entry.remote.apiKey);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const tensor = await entry.pipe._call(text, { pooling: entry.model.pooling, normalize: entry.model.normalize });
|
|
161
|
+
vec = Array.from(tensor.data);
|
|
162
|
+
}
|
|
163
|
+
entry.cache.set(text, vec);
|
|
164
|
+
return vec;
|
|
165
|
+
}
|
|
166
|
+
/** Embed a search query. Applies queryPrefix and configured pooling. */
|
|
167
|
+
async function embedQuery(query, modelName = 'default') {
|
|
168
|
+
const entry = getEntry(modelName);
|
|
169
|
+
const text = `${entry.model.queryPrefix}${query}`.slice(0, entry.maxChars);
|
|
170
|
+
const cached = entry.cache.get(text);
|
|
171
|
+
if (cached)
|
|
172
|
+
return cached;
|
|
173
|
+
let vec;
|
|
174
|
+
if (entry.remote) {
|
|
175
|
+
[vec] = await remoteEmbed(entry.remote.url, [text], entry.remote.apiKey);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
const tensor = await entry.pipe._call(text, { pooling: entry.model.pooling, normalize: entry.model.normalize });
|
|
179
|
+
vec = Array.from(tensor.data);
|
|
180
|
+
}
|
|
181
|
+
entry.cache.set(text, vec);
|
|
182
|
+
return vec;
|
|
183
|
+
}
|
|
184
|
+
/** Batch-embed documents (indexing). Applies documentPrefix and configured pooling. */
|
|
185
|
+
async function embedBatch(inputs, modelName = 'default') {
|
|
186
|
+
const entry = getEntry(modelName);
|
|
187
|
+
if (inputs.length === 0)
|
|
188
|
+
return [];
|
|
189
|
+
if (inputs.length === 1)
|
|
190
|
+
return [await embed(inputs[0].title, inputs[0].content, modelName)];
|
|
191
|
+
const texts = inputs.map(({ title, content }) => `${entry.model.documentPrefix}${title}\n${content}`.slice(0, entry.maxChars));
|
|
192
|
+
// Check cache: split into hits and misses
|
|
193
|
+
const result = texts.map(t => entry.cache.get(t) ?? null);
|
|
194
|
+
const missIndices = result.map((v, i) => v === null ? i : -1).filter(i => i >= 0);
|
|
195
|
+
if (missIndices.length === 0)
|
|
196
|
+
return result;
|
|
197
|
+
const missTexts = missIndices.map(i => texts[i]);
|
|
198
|
+
let missVecs;
|
|
199
|
+
if (entry.remote) {
|
|
200
|
+
missVecs = await remoteEmbed(entry.remote.url, missTexts, entry.remote.apiKey);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
missVecs = [];
|
|
204
|
+
const batchSize = entry.embedding.batchSize;
|
|
205
|
+
for (let start = 0; start < missTexts.length; start += batchSize) {
|
|
206
|
+
const chunk = missTexts.slice(start, start + batchSize);
|
|
207
|
+
const tensor = await entry.pipe._call(chunk, { pooling: entry.model.pooling, normalize: entry.model.normalize });
|
|
208
|
+
const dim = tensor.dims[1];
|
|
209
|
+
const data = tensor.data;
|
|
210
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
211
|
+
missVecs.push(Array.from(data.slice(i * dim, (i + 1) * dim)));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// Fill results and populate cache
|
|
216
|
+
for (let j = 0; j < missIndices.length; j++) {
|
|
217
|
+
const idx = missIndices[j];
|
|
218
|
+
result[idx] = missVecs[j];
|
|
219
|
+
entry.cache.set(texts[idx], missVecs[j]);
|
|
220
|
+
}
|
|
221
|
+
return result;
|
|
222
|
+
}
|
|
223
|
+
function resetEmbedder() {
|
|
224
|
+
_models.clear();
|
|
225
|
+
_pipeCache.clear();
|
|
226
|
+
}
|
|
227
|
+
// Vectors are L2-normalized → dot product = cosine similarity
|
|
228
|
+
function cosineSimilarity(a, b) {
|
|
229
|
+
if (a.length !== b.length)
|
|
230
|
+
return 0;
|
|
231
|
+
let dot = 0;
|
|
232
|
+
for (let i = 0; i < a.length; i++)
|
|
233
|
+
dot += a[i] * b[i];
|
|
234
|
+
return dot;
|
|
235
|
+
}
|
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.appendEvent = appendEvent;
|
|
37
|
+
exports.readEvents = readEvents;
|
|
38
|
+
exports.replayNoteEvents = replayNoteEvents;
|
|
39
|
+
exports.replayTaskEvents = replayTaskEvents;
|
|
40
|
+
exports.replaySkillEvents = replaySkillEvents;
|
|
41
|
+
exports.ensureGitignore = ensureGitignore;
|
|
42
|
+
exports.ensureGitattributes = ensureGitattributes;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Core functions
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
/** Append a single event as a JSON line to the events.jsonl file. */
|
|
49
|
+
function appendEvent(eventsPath, event) {
|
|
50
|
+
try {
|
|
51
|
+
const line = JSON.stringify({ ts: new Date().toISOString(), ...event }) + '\n';
|
|
52
|
+
fs.appendFileSync(eventsPath, line, 'utf-8');
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
process.stderr.write(`[events-log] failed to append event: ${err}\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Read and parse all events from a JSONL file. Invalid lines are skipped. */
|
|
59
|
+
function readEvents(eventsPath) {
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(eventsPath))
|
|
62
|
+
return [];
|
|
63
|
+
const content = fs.readFileSync(eventsPath, 'utf-8');
|
|
64
|
+
const events = [];
|
|
65
|
+
for (const line of content.split('\n')) {
|
|
66
|
+
const trimmed = line.trim();
|
|
67
|
+
if (!trimmed)
|
|
68
|
+
continue;
|
|
69
|
+
try {
|
|
70
|
+
events.push(JSON.parse(trimmed));
|
|
71
|
+
}
|
|
72
|
+
catch { /* skip invalid lines */ }
|
|
73
|
+
}
|
|
74
|
+
return events;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Sort events by timestamp (ISO strings are lexicographically sortable). */
|
|
81
|
+
function sortByTs(events) {
|
|
82
|
+
return [...events].sort((a, b) => (a.ts < b.ts ? -1 : a.ts > b.ts ? 1 : 0));
|
|
83
|
+
}
|
|
84
|
+
function isoToMs(value) {
|
|
85
|
+
if (value == null)
|
|
86
|
+
return null;
|
|
87
|
+
const d = new Date(value);
|
|
88
|
+
return isNaN(d.getTime()) ? null : d.getTime();
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Replay functions
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Replay note events + content to reconstruct a ParsedNoteFile.
|
|
95
|
+
* Returns null if no 'created' event is found.
|
|
96
|
+
*/
|
|
97
|
+
function replayNoteEvents(events, content) {
|
|
98
|
+
const sorted = sortByTs(events);
|
|
99
|
+
const created = sorted.find(e => e.op === 'created');
|
|
100
|
+
if (!created)
|
|
101
|
+
return null;
|
|
102
|
+
let title = created.title;
|
|
103
|
+
let tags = created.tags ?? [];
|
|
104
|
+
let updatedBy = null;
|
|
105
|
+
const relations = [];
|
|
106
|
+
const attachments = [];
|
|
107
|
+
for (const ev of sorted) {
|
|
108
|
+
if (ev.op === 'update') {
|
|
109
|
+
const u = ev;
|
|
110
|
+
if (typeof u.title === 'string')
|
|
111
|
+
title = u.title;
|
|
112
|
+
if (Array.isArray(u.tags))
|
|
113
|
+
tags = u.tags;
|
|
114
|
+
if (typeof u.by === 'string')
|
|
115
|
+
updatedBy = u.by;
|
|
116
|
+
}
|
|
117
|
+
else if (ev.op === 'relation') {
|
|
118
|
+
const r = ev;
|
|
119
|
+
const key = `${r.to}:${r.kind}:${r.graph ?? ''}`;
|
|
120
|
+
if (r.action === 'add') {
|
|
121
|
+
if (!relations.some(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key)) {
|
|
122
|
+
const entry = { to: r.to, kind: r.kind };
|
|
123
|
+
if (r.graph)
|
|
124
|
+
entry.graph = r.graph;
|
|
125
|
+
relations.push(entry);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const idx = relations.findIndex(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key);
|
|
130
|
+
if (idx !== -1)
|
|
131
|
+
relations.splice(idx, 1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else if (ev.op === 'attachment') {
|
|
135
|
+
const a = ev;
|
|
136
|
+
if (a.action === 'add') {
|
|
137
|
+
if (!attachments.some(x => x.filename === a.file)) {
|
|
138
|
+
attachments.push({ filename: a.file, mimeType: 'application/octet-stream', size: 0, addedAt: 0 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const idx = attachments.findIndex(x => x.filename === a.file);
|
|
143
|
+
if (idx !== -1)
|
|
144
|
+
attachments.splice(idx, 1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const version = sorted.length;
|
|
149
|
+
// updatedAt = ts of last event; createdAt from created event
|
|
150
|
+
const lastEvent = sorted[sorted.length - 1];
|
|
151
|
+
const updatedAt = isoToMs(lastEvent?.ts);
|
|
152
|
+
const createdAt = isoToMs(created.ts) ?? created.createdAt;
|
|
153
|
+
return {
|
|
154
|
+
id: created.id,
|
|
155
|
+
title,
|
|
156
|
+
content,
|
|
157
|
+
tags,
|
|
158
|
+
createdAt: created.createdAt ?? createdAt,
|
|
159
|
+
updatedAt: updatedAt ?? created.createdAt,
|
|
160
|
+
version,
|
|
161
|
+
createdBy: created.createdBy ?? null,
|
|
162
|
+
updatedBy,
|
|
163
|
+
relations,
|
|
164
|
+
attachments,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Replay task events + description to reconstruct a ParsedTaskFile.
|
|
169
|
+
* Returns null if no 'created' event is found.
|
|
170
|
+
*/
|
|
171
|
+
function replayTaskEvents(events, description) {
|
|
172
|
+
const sorted = sortByTs(events);
|
|
173
|
+
const created = sorted.find(e => e.op === 'created');
|
|
174
|
+
if (!created)
|
|
175
|
+
return null;
|
|
176
|
+
let title = created.title;
|
|
177
|
+
let status = created.status;
|
|
178
|
+
let priority = created.priority;
|
|
179
|
+
let tags = created.tags ?? [];
|
|
180
|
+
let dueDate = created.dueDate;
|
|
181
|
+
let estimate = created.estimate;
|
|
182
|
+
let completedAt = created.completedAt;
|
|
183
|
+
let updatedBy = null;
|
|
184
|
+
const relations = [];
|
|
185
|
+
const attachments = [];
|
|
186
|
+
for (const ev of sorted) {
|
|
187
|
+
if (ev.op === 'update') {
|
|
188
|
+
const u = ev;
|
|
189
|
+
if (typeof u.title === 'string')
|
|
190
|
+
title = u.title;
|
|
191
|
+
if (typeof u.status === 'string')
|
|
192
|
+
status = u.status;
|
|
193
|
+
if (typeof u.priority === 'string')
|
|
194
|
+
priority = u.priority;
|
|
195
|
+
if (Array.isArray(u.tags))
|
|
196
|
+
tags = u.tags;
|
|
197
|
+
if ('dueDate' in u)
|
|
198
|
+
dueDate = u.dueDate;
|
|
199
|
+
if ('estimate' in u)
|
|
200
|
+
estimate = u.estimate;
|
|
201
|
+
if ('completedAt' in u)
|
|
202
|
+
completedAt = u.completedAt;
|
|
203
|
+
if (typeof u.by === 'string')
|
|
204
|
+
updatedBy = u.by;
|
|
205
|
+
}
|
|
206
|
+
else if (ev.op === 'relation') {
|
|
207
|
+
const r = ev;
|
|
208
|
+
const key = `${r.to}:${r.kind}:${r.graph ?? ''}`;
|
|
209
|
+
if (r.action === 'add') {
|
|
210
|
+
if (!relations.some(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key)) {
|
|
211
|
+
const entry = { to: r.to, kind: r.kind };
|
|
212
|
+
if (r.graph)
|
|
213
|
+
entry.graph = r.graph;
|
|
214
|
+
relations.push(entry);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
const idx = relations.findIndex(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key);
|
|
219
|
+
if (idx !== -1)
|
|
220
|
+
relations.splice(idx, 1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
else if (ev.op === 'attachment') {
|
|
224
|
+
const a = ev;
|
|
225
|
+
if (a.action === 'add') {
|
|
226
|
+
if (!attachments.some(x => x.filename === a.file)) {
|
|
227
|
+
attachments.push({ filename: a.file, mimeType: 'application/octet-stream', size: 0, addedAt: 0 });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
const idx = attachments.findIndex(x => x.filename === a.file);
|
|
232
|
+
if (idx !== -1)
|
|
233
|
+
attachments.splice(idx, 1);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const version = sorted.length;
|
|
238
|
+
const lastEvent = sorted[sorted.length - 1];
|
|
239
|
+
const updatedAt = isoToMs(lastEvent?.ts);
|
|
240
|
+
return {
|
|
241
|
+
id: created.id,
|
|
242
|
+
title,
|
|
243
|
+
description,
|
|
244
|
+
status,
|
|
245
|
+
priority,
|
|
246
|
+
tags,
|
|
247
|
+
dueDate,
|
|
248
|
+
estimate,
|
|
249
|
+
completedAt,
|
|
250
|
+
assignee: null,
|
|
251
|
+
createdAt: created.createdAt,
|
|
252
|
+
updatedAt: updatedAt ?? created.createdAt,
|
|
253
|
+
version,
|
|
254
|
+
createdBy: created.createdBy ?? null,
|
|
255
|
+
updatedBy,
|
|
256
|
+
relations,
|
|
257
|
+
attachments,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Replay skill events + description to reconstruct a ParsedSkillFile.
|
|
262
|
+
* Returns null if no 'created' event is found.
|
|
263
|
+
*/
|
|
264
|
+
function replaySkillEvents(events, description) {
|
|
265
|
+
const sorted = sortByTs(events);
|
|
266
|
+
const created = sorted.find(e => e.op === 'created');
|
|
267
|
+
if (!created)
|
|
268
|
+
return null;
|
|
269
|
+
let title = created.title;
|
|
270
|
+
let tags = created.tags ?? [];
|
|
271
|
+
let steps = created.steps ?? [];
|
|
272
|
+
let triggers = created.triggers ?? [];
|
|
273
|
+
let inputHints = created.inputHints ?? [];
|
|
274
|
+
let filePatterns = created.filePatterns ?? [];
|
|
275
|
+
let source = created.source;
|
|
276
|
+
let confidence = created.confidence;
|
|
277
|
+
let usageCount = created.usageCount;
|
|
278
|
+
let lastUsedAt = created.lastUsedAt;
|
|
279
|
+
let updatedBy = null;
|
|
280
|
+
const relations = [];
|
|
281
|
+
const attachments = [];
|
|
282
|
+
for (const ev of sorted) {
|
|
283
|
+
if (ev.op === 'update') {
|
|
284
|
+
const u = ev;
|
|
285
|
+
if (typeof u.title === 'string')
|
|
286
|
+
title = u.title;
|
|
287
|
+
if (Array.isArray(u.tags))
|
|
288
|
+
tags = u.tags;
|
|
289
|
+
if (Array.isArray(u.steps))
|
|
290
|
+
steps = u.steps;
|
|
291
|
+
if (Array.isArray(u.triggers))
|
|
292
|
+
triggers = u.triggers;
|
|
293
|
+
if (Array.isArray(u.inputHints))
|
|
294
|
+
inputHints = u.inputHints;
|
|
295
|
+
if (Array.isArray(u.filePatterns))
|
|
296
|
+
filePatterns = u.filePatterns;
|
|
297
|
+
if (typeof u.source === 'string')
|
|
298
|
+
source = u.source;
|
|
299
|
+
if (typeof u.confidence === 'number')
|
|
300
|
+
confidence = u.confidence;
|
|
301
|
+
if (typeof u.usageCount === 'number')
|
|
302
|
+
usageCount = u.usageCount;
|
|
303
|
+
if ('lastUsedAt' in u)
|
|
304
|
+
lastUsedAt = u.lastUsedAt;
|
|
305
|
+
if (typeof u.by === 'string')
|
|
306
|
+
updatedBy = u.by;
|
|
307
|
+
}
|
|
308
|
+
else if (ev.op === 'relation') {
|
|
309
|
+
const r = ev;
|
|
310
|
+
const key = `${r.to}:${r.kind}:${r.graph ?? ''}`;
|
|
311
|
+
if (r.action === 'add') {
|
|
312
|
+
if (!relations.some(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key)) {
|
|
313
|
+
const entry = { to: r.to, kind: r.kind };
|
|
314
|
+
if (r.graph)
|
|
315
|
+
entry.graph = r.graph;
|
|
316
|
+
relations.push(entry);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const idx = relations.findIndex(x => `${x.to}:${x.kind}:${x.graph ?? ''}` === key);
|
|
321
|
+
if (idx !== -1)
|
|
322
|
+
relations.splice(idx, 1);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
else if (ev.op === 'attachment') {
|
|
326
|
+
const a = ev;
|
|
327
|
+
if (a.action === 'add') {
|
|
328
|
+
if (!attachments.some(x => x.filename === a.file)) {
|
|
329
|
+
attachments.push({ filename: a.file, mimeType: 'application/octet-stream', size: 0, addedAt: 0 });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
const idx = attachments.findIndex(x => x.filename === a.file);
|
|
334
|
+
if (idx !== -1)
|
|
335
|
+
attachments.splice(idx, 1);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const version = sorted.length;
|
|
340
|
+
const lastEvent = sorted[sorted.length - 1];
|
|
341
|
+
const updatedAt = isoToMs(lastEvent?.ts);
|
|
342
|
+
return {
|
|
343
|
+
id: created.id,
|
|
344
|
+
title,
|
|
345
|
+
description,
|
|
346
|
+
steps,
|
|
347
|
+
triggers,
|
|
348
|
+
inputHints,
|
|
349
|
+
filePatterns,
|
|
350
|
+
tags,
|
|
351
|
+
source,
|
|
352
|
+
confidence,
|
|
353
|
+
usageCount,
|
|
354
|
+
lastUsedAt,
|
|
355
|
+
createdAt: created.createdAt,
|
|
356
|
+
updatedAt: updatedAt ?? created.createdAt,
|
|
357
|
+
version,
|
|
358
|
+
createdBy: created.createdBy ?? null,
|
|
359
|
+
updatedBy,
|
|
360
|
+
relations,
|
|
361
|
+
attachments,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
// ---------------------------------------------------------------------------
|
|
365
|
+
// Git helpers
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
/** Ensure a .gitignore file in parentDir contains the given pattern line. */
|
|
368
|
+
function ensureGitignore(parentDir, pattern) {
|
|
369
|
+
try {
|
|
370
|
+
const gitignorePath = path.join(parentDir, '.gitignore');
|
|
371
|
+
let content = '';
|
|
372
|
+
if (fs.existsSync(gitignorePath)) {
|
|
373
|
+
content = fs.readFileSync(gitignorePath, 'utf-8');
|
|
374
|
+
}
|
|
375
|
+
if (!content.split('\n').some(l => l.trim() === pattern)) {
|
|
376
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
377
|
+
fs.writeFileSync(gitignorePath, content + (content && !content.endsWith('\n') ? '\n' : '') + pattern + '\n', 'utf-8');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (err) {
|
|
381
|
+
process.stderr.write(`[events-log] failed to write .gitignore: ${err}\n`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/** Ensure a .gitattributes file in entityParentDir contains the merge=union line for events.jsonl. */
|
|
385
|
+
function ensureGitattributes(entityParentDir) {
|
|
386
|
+
const pattern = '*/events.jsonl merge=union';
|
|
387
|
+
try {
|
|
388
|
+
const gitattrsPath = path.join(entityParentDir, '.gitattributes');
|
|
389
|
+
let content = '';
|
|
390
|
+
if (fs.existsSync(gitattrsPath)) {
|
|
391
|
+
content = fs.readFileSync(gitattrsPath, 'utf-8');
|
|
392
|
+
}
|
|
393
|
+
if (!content.split('\n').some(l => l.trim() === pattern)) {
|
|
394
|
+
fs.mkdirSync(entityParentDir, { recursive: true });
|
|
395
|
+
fs.writeFileSync(gitattrsPath, content + (content && !content.endsWith('\n') ? '\n' : '') + pattern + '\n', 'utf-8');
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch (err) {
|
|
399
|
+
process.stderr.write(`[events-log] failed to write .gitattributes: ${err}\n`);
|
|
400
|
+
}
|
|
401
|
+
}
|