modular-studio 1.0.5 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -122
- package/dist/assets/{Badge-22Ai0eyi.js → Badge-DrUmDAXz.js} +1 -1
- package/dist/assets/{Input-Bgp734xs.js → Input-ndEGQSgx.js} +1 -1
- package/dist/assets/KnowledgeTab-CxlC76Rf.js +4 -0
- package/dist/assets/MemoryTab-CUScYWs9.js +16 -0
- package/dist/assets/QualificationTab-BqnWSQHm.js +1 -0
- package/dist/assets/ReviewTab-DKYl6cR9.js +103 -0
- package/dist/assets/{Section-DoJrmytO.js → Section-CgmwAj_2.js} +1 -1
- package/dist/assets/{TestTab-PDyMF8Fw.js → TestTab-iJ2vCf9l.js} +15 -15
- package/dist/assets/ToolsTab-C10Ulm8b.js +1 -0
- package/dist/assets/icons-MKpPNvV8.js +1 -0
- package/dist/assets/index-B_ip7Amg.css +1 -0
- package/dist/assets/index-gBy3427k.js +143 -0
- package/dist/assets/services-CTWXQK6j.js +356 -0
- package/dist/index.html +18 -18
- package/dist-server/server/index.d.ts.map +1 -1
- package/dist-server/server/index.js +4 -0
- package/dist-server/server/mcp/manager.d.ts.map +1 -1
- package/dist-server/server/mcp/manager.js +16 -2
- package/dist-server/server/routes/agents.d.ts.map +1 -1
- package/dist-server/server/routes/agents.js +27 -0
- package/dist-server/server/routes/cache.d.ts +3 -0
- package/dist-server/server/routes/cache.d.ts.map +1 -0
- package/dist-server/server/routes/cache.js +55 -0
- package/dist-server/server/routes/connectors.d.ts.map +1 -1
- package/dist-server/server/routes/connectors.js +47 -17
- package/dist-server/server/routes/lessons.d.ts +3 -0
- package/dist-server/server/routes/lessons.d.ts.map +1 -0
- package/dist-server/server/routes/lessons.js +46 -0
- package/dist-server/server/routes/memory.d.ts.map +1 -1
- package/dist-server/server/routes/memory.js +31 -0
- package/dist-server/server/routes/qualification.d.ts.map +1 -1
- package/dist-server/server/routes/qualification.js +292 -334
- package/dist-server/server/routes/repo-index.d.ts.map +1 -1
- package/dist-server/server/routes/repo-index.js +7 -0
- package/dist-server/server/routes/skills-search.d.ts.map +1 -1
- package/dist-server/server/routes/skills-search.js +182 -26
- package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
- package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
- package/dist-server/server/services/adapters/postgresAdapter.js +30 -30
- package/dist-server/server/services/adapters/sqliteAdapter.js +29 -29
- package/dist-server/server/services/agentStore.d.ts +2 -1
- package/dist-server/server/services/agentStore.d.ts.map +1 -1
- package/dist-server/server/services/agentStore.js +2 -1
- package/dist-server/server/services/correctionDetector.d.ts +22 -0
- package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
- package/dist-server/server/services/correctionDetector.js +91 -0
- package/dist-server/server/services/hindsightClient.d.ts +15 -0
- package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
- package/dist-server/server/services/hindsightClient.js +47 -0
- package/dist-server/server/services/lessonExtractor.d.ts +19 -0
- package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
- package/dist-server/server/services/lessonExtractor.js +87 -0
- package/dist-server/server/services/responseCache.d.ts +24 -0
- package/dist-server/server/services/responseCache.d.ts.map +1 -0
- package/dist-server/server/services/responseCache.js +163 -0
- package/dist-server/server/services/sqliteStore.d.ts +8 -0
- package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
- package/dist-server/server/services/sqliteStore.js +53 -13
- package/dist-server/src/store/knowledgeBase.d.ts +1 -0
- package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
- package/dist-server/src/store/lessonStore.d.ts +26 -0
- package/dist-server/src/store/lessonStore.d.ts.map +1 -0
- package/dist-server/src/store/lessonStore.js +64 -0
- package/dist-server/src/store/memoryStore.d.ts +12 -1
- package/dist-server/src/store/memoryStore.d.ts.map +1 -1
- package/dist-server/src/store/memoryStore.js +9 -0
- package/dist-server/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +105 -104
- package/dist/assets/KnowledgeTab-DABxirZh.js +0 -4
- package/dist/assets/MemoryTab-DZeYElIT.js +0 -16
- package/dist/assets/QualificationTab-Dfpy3J30.js +0 -1
- package/dist/assets/ReviewTab-SD8lQuCc.js +0 -103
- package/dist/assets/ToolsTab-B83qGCmG.js +0 -1
- package/dist/assets/icons-C2EV-le6.js +0 -1
- package/dist/assets/index-DkpMAxX7.css +0 -1
- package/dist/assets/index-q24ug5Qs.js +0 -143
- package/dist/assets/services-BaKotDf0.js +0 -343
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repo-index.d.ts","sourceRoot":"","sources":["../../../server/routes/repo-index.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAOxB,wBAAgB,gCAAgC,IAAI,MAAM,CA0BzD;
|
|
1
|
+
{"version":3,"file":"repo-index.d.ts","sourceRoot":"","sources":["../../../server/routes/repo-index.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAOxB,wBAAgB,gCAAgC,IAAI,MAAM,CA0BzD;AAgUD,eAAe,MAAM,CAAC"}
|
|
@@ -226,6 +226,12 @@ router.post('/index-github', async (req, res) => {
|
|
|
226
226
|
})),
|
|
227
227
|
},
|
|
228
228
|
});
|
|
229
|
+
const CODE_EXTS = /\.(ts|tsx|js|jsx|py)$/;
|
|
230
|
+
const codeFiles = result.clonePath
|
|
231
|
+
? result.scan.files
|
|
232
|
+
.filter((f) => CODE_EXTS.test(f.path))
|
|
233
|
+
.map((f) => join(result.clonePath, f.path))
|
|
234
|
+
: [];
|
|
229
235
|
res.json({
|
|
230
236
|
status: 'ok',
|
|
231
237
|
data: {
|
|
@@ -233,6 +239,7 @@ router.post('/index-github', async (req, res) => {
|
|
|
233
239
|
clonePath: result.clonePath,
|
|
234
240
|
outputDir: outDir,
|
|
235
241
|
files: written,
|
|
242
|
+
codeFiles,
|
|
236
243
|
overviewMarkdown: result.overviewMarkdown,
|
|
237
244
|
fullMarkdown: result.fullMarkdown,
|
|
238
245
|
knowledgeDocs: docsObj,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-search.d.ts","sourceRoot":"","sources":["../../../server/routes/skills-search.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"skills-search.d.ts","sourceRoot":"","sources":["../../../server/routes/skills-search.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0bxB,eAAe,MAAM,CAAC"}
|
|
@@ -120,6 +120,36 @@ router.get('/audit/:owner/:repo/:skill', async (req, res) => {
|
|
|
120
120
|
res.status(500).json({ error: message, gen: 'Pending', socket: 'Pending', snyk: 'Pending' });
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
+
// Parse `npx skills find` output into structured results
|
|
124
|
+
function parseSkillsFindOutput(output) {
|
|
125
|
+
const results = [];
|
|
126
|
+
// Each skill appears as: owner/repo@skillName followed by install count
|
|
127
|
+
// e.g. "vercel-labs/agent-skills@vercel-react-best-practices 226.6K installs"
|
|
128
|
+
// "└ https://skills.sh/vercel-labs/agent-skills/vercel-react-best-practices"
|
|
129
|
+
const lines = output.split('\n');
|
|
130
|
+
for (let i = 0; i < lines.length; i++) {
|
|
131
|
+
// Strip ANSI escape codes
|
|
132
|
+
const clean = lines[i].replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
133
|
+
// Match: owner/repo@skillName NNK installs
|
|
134
|
+
const m = clean.match(/^([a-z0-9_.-]+\/[a-z0-9_.-]+)@([a-z0-9_:.-]+)\s+([\d,.]+[KkMm]?)\s*installs?/i);
|
|
135
|
+
if (m) {
|
|
136
|
+
const repo = m[1];
|
|
137
|
+
const name = m[2];
|
|
138
|
+
const installs = m[3];
|
|
139
|
+
// Next line might have the URL
|
|
140
|
+
const nextClean = (lines[i + 1] || '').replace(/\x1b\[[0-9;]*m/g, '').trim();
|
|
141
|
+
const urlMatch = nextClean.match(/(https:\/\/skills\.sh\/[^\s]+)/);
|
|
142
|
+
results.push({
|
|
143
|
+
id: `${repo}@${name}`,
|
|
144
|
+
name,
|
|
145
|
+
repo,
|
|
146
|
+
installs,
|
|
147
|
+
url: urlMatch ? urlMatch[1] : `https://skills.sh/${repo}/${name}`,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
123
153
|
// GET /api/skills/search?q=react
|
|
124
154
|
router.get('/search', async (req, res) => {
|
|
125
155
|
const query = req.query.q || '';
|
|
@@ -127,6 +157,23 @@ router.get('/search', async (req, res) => {
|
|
|
127
157
|
res.json({ data: [], query });
|
|
128
158
|
return;
|
|
129
159
|
}
|
|
160
|
+
// Try `npx skills find <query>` first — uses the official skills.sh CLI
|
|
161
|
+
try {
|
|
162
|
+
const findCmd = `npx -y skills find ${query.replace(/[^a-z0-9_ -]/gi, '')}`;
|
|
163
|
+
const { stdout } = await exec(findCmd, [], {
|
|
164
|
+
timeout: 30000,
|
|
165
|
+
shell: true,
|
|
166
|
+
});
|
|
167
|
+
const cliResults = parseSkillsFindOutput(String(stdout));
|
|
168
|
+
if (cliResults.length > 0) {
|
|
169
|
+
res.json({ data: cliResults.slice(0, 10), query, source: 'skills-cli' });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// CLI not available or failed — fall back to catalog scraping
|
|
175
|
+
}
|
|
176
|
+
// Fallback: scrape skills.sh catalog
|
|
130
177
|
try {
|
|
131
178
|
const catalog = await fetchCatalog();
|
|
132
179
|
const q = query.toLowerCase();
|
|
@@ -168,7 +215,7 @@ router.get('/search', async (req, res) => {
|
|
|
168
215
|
url: entry.url,
|
|
169
216
|
};
|
|
170
217
|
});
|
|
171
|
-
res.json({ data: results, query });
|
|
218
|
+
res.json({ data: results, query, source: 'catalog-scrape' });
|
|
172
219
|
}
|
|
173
220
|
catch (err) {
|
|
174
221
|
const message = err instanceof Error ? err.message : 'Search failed';
|
|
@@ -188,42 +235,151 @@ router.post('/install', async (req, res) => {
|
|
|
188
235
|
return;
|
|
189
236
|
}
|
|
190
237
|
try {
|
|
191
|
-
// Try the
|
|
192
|
-
let args = ['-y', '@anthropic/skills', 'add', skillId];
|
|
193
|
-
if (scope === 'global')
|
|
194
|
-
args.push('-g');
|
|
238
|
+
// Try the skills CLI first: `npx skills add <skillId> -y -g`
|
|
195
239
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
240
|
+
// Use single command string to avoid DEP0190 deprecation warning
|
|
241
|
+
const safeSkillId = skillId.replace(/[^a-z0-9@/_.-]/gi, '');
|
|
242
|
+
const addCmd = scope === 'global'
|
|
243
|
+
? `npx -y skills add ${safeSkillId} -y -g`
|
|
244
|
+
: `npx -y skills add ${safeSkillId} -y`;
|
|
245
|
+
const { stdout, stderr } = await exec(addCmd, [], { timeout: 60000, shell: true });
|
|
246
|
+
res.json({ status: 'ok', output: String(stdout) + String(stderr) });
|
|
198
247
|
return;
|
|
199
248
|
}
|
|
200
249
|
catch (cliError) {
|
|
201
|
-
console.log('Skills CLI failed, trying fallback:', cliError.message);
|
|
202
|
-
}
|
|
203
|
-
// Fallback: Direct download from skills.sh
|
|
204
|
-
console.log('Using fallback: downloading skill directly from skills.sh');
|
|
205
|
-
// Extract repo and skill name from skillId (format: owner/repo@skillName)
|
|
206
|
-
const [repoPath, skillName] = skillId.includes('@') ? skillId.split('@') : [skillId, skillId.split('/').pop() || skillId];
|
|
207
|
-
// Download skill content from skills.sh
|
|
208
|
-
const skillUrl = `https://raw.githubusercontent.com/${repoPath}/main/${skillName}/SKILL.md`;
|
|
209
|
-
const skillResponse = await fetch(skillUrl);
|
|
210
|
-
if (!skillResponse.ok) {
|
|
211
|
-
throw new Error(`Failed to download skill from ${skillUrl}: ${skillResponse.status}`);
|
|
250
|
+
console.log('Skills CLI (npx skills add) failed, trying GitHub fallback:', cliError.message);
|
|
212
251
|
}
|
|
213
|
-
|
|
214
|
-
//
|
|
252
|
+
// Fallback: Download full skill directory from GitHub via API
|
|
253
|
+
// Repos like anthropics/knowledge-work-plugins nest skills deeply:
|
|
254
|
+
// <category>/skills/<skillName>/SKILL.md
|
|
255
|
+
console.log('Using fallback: downloading skill from GitHub API');
|
|
215
256
|
const os = await import('os');
|
|
216
257
|
const path = await import('path');
|
|
217
258
|
const fs = await import('fs/promises');
|
|
218
|
-
|
|
219
|
-
const
|
|
220
|
-
|
|
259
|
+
// Extract repo and skill name from skillId (format: owner/repo@skillName)
|
|
260
|
+
const [repoPath, skillName] = skillId.includes('@') ? skillId.split('@') : [skillId, skillId.split('/').pop() || skillId];
|
|
261
|
+
const [owner, repo] = repoPath.split('/');
|
|
262
|
+
// Use the Git Trees API to find SKILL.md anywhere in the repo
|
|
263
|
+
// This handles arbitrarily nested structures like category/skills/name/SKILL.md
|
|
264
|
+
let treeData = [];
|
|
265
|
+
let treePath = '';
|
|
266
|
+
try {
|
|
267
|
+
const treeRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/trees/HEAD?recursive=1`, {
|
|
268
|
+
headers: { 'User-Agent': 'modular-patchbay/1.0', 'Accept': 'application/vnd.github.v3+json' },
|
|
269
|
+
signal: AbortSignal.timeout(15000),
|
|
270
|
+
});
|
|
271
|
+
if (treeRes.ok) {
|
|
272
|
+
const treeJson = await treeRes.json();
|
|
273
|
+
const allFiles = treeJson.tree || [];
|
|
274
|
+
// Find SKILL.md in a directory matching the skill name
|
|
275
|
+
const skillMdEntries = allFiles.filter(f => f.type === 'blob' && f.path.endsWith('/SKILL.md'));
|
|
276
|
+
// Match: exact directory name, or directory ends with skillName
|
|
277
|
+
const match = skillMdEntries.find(f => {
|
|
278
|
+
const dir = f.path.replace('/SKILL.md', '');
|
|
279
|
+
const dirName = dir.split('/').pop();
|
|
280
|
+
return dirName === skillName;
|
|
281
|
+
});
|
|
282
|
+
if (match) {
|
|
283
|
+
treePath = match.path.replace('/SKILL.md', '');
|
|
284
|
+
// Now fetch that directory's contents via Contents API
|
|
285
|
+
const dirRes = await fetch(`https://api.github.com/repos/${owner}/${repo}/contents/${treePath}`, {
|
|
286
|
+
headers: { 'User-Agent': 'modular-patchbay/1.0', 'Accept': 'application/vnd.github.v3+json' },
|
|
287
|
+
signal: AbortSignal.timeout(10000),
|
|
288
|
+
});
|
|
289
|
+
if (dirRes.ok) {
|
|
290
|
+
const dirData = await dirRes.json();
|
|
291
|
+
if (Array.isArray(dirData)) {
|
|
292
|
+
treeData = dirData;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
// Tree API failed, continue to static path fallback
|
|
300
|
+
}
|
|
301
|
+
// Static path fallback if tree search didn't find it
|
|
302
|
+
if (treeData.length === 0) {
|
|
303
|
+
const candidatePaths = [skillName, `skills/${skillName}`, `src/${skillName}`];
|
|
304
|
+
for (const cp of candidatePaths) {
|
|
305
|
+
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${cp}`;
|
|
306
|
+
const apiRes = await fetch(apiUrl, {
|
|
307
|
+
headers: { 'User-Agent': 'modular-patchbay/1.0', 'Accept': 'application/vnd.github.v3+json' },
|
|
308
|
+
signal: AbortSignal.timeout(10000),
|
|
309
|
+
});
|
|
310
|
+
if (apiRes.ok) {
|
|
311
|
+
const data = await apiRes.json();
|
|
312
|
+
if (Array.isArray(data) && data.some((f) => f.name === 'SKILL.md')) {
|
|
313
|
+
treeData = data;
|
|
314
|
+
treePath = cp;
|
|
315
|
+
break;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Last resort: raw SKILL.md download
|
|
321
|
+
if (treeData.length === 0) {
|
|
322
|
+
const base = `https://raw.githubusercontent.com/${repoPath}`;
|
|
323
|
+
const rawCandidates = [
|
|
324
|
+
`${base}/main/${skillName}/SKILL.md`,
|
|
325
|
+
`${base}/main/SKILL.md`,
|
|
326
|
+
`${base}/master/${skillName}/SKILL.md`,
|
|
327
|
+
`${base}/master/SKILL.md`,
|
|
328
|
+
];
|
|
329
|
+
let skillContent = null;
|
|
330
|
+
for (const url of rawCandidates) {
|
|
331
|
+
const r = await fetch(url);
|
|
332
|
+
if (r.ok) {
|
|
333
|
+
skillContent = await r.text();
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
if (skillContent === null) {
|
|
338
|
+
throw new Error(`Skill "${skillName}" not found in ${repoPath}. Tried: skills CLI, GitHub tree search, static paths, and raw URLs.`);
|
|
339
|
+
}
|
|
340
|
+
const skillDir = path.join(os.homedir(), '.agents', 'skills', skillName);
|
|
341
|
+
await fs.mkdir(skillDir, { recursive: true });
|
|
342
|
+
await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent, 'utf8');
|
|
343
|
+
res.json({ status: 'ok', output: `Skill ${skillName} installed (SKILL.md only — directory listing unavailable)` });
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
// Download all files in the skill directory (including subdirs like references/, scripts/)
|
|
347
|
+
const skillDir = path.join(os.homedir(), '.agents', 'skills', skillName);
|
|
221
348
|
await fs.mkdir(skillDir, { recursive: true });
|
|
222
|
-
|
|
223
|
-
|
|
349
|
+
const downloadDir = async (items, localDir) => {
|
|
350
|
+
let count = 0;
|
|
351
|
+
for (const item of items) {
|
|
352
|
+
if (item.type === 'file' && item.download_url) {
|
|
353
|
+
const fileRes = await fetch(item.download_url);
|
|
354
|
+
if (fileRes.ok) {
|
|
355
|
+
const content = await fileRes.text();
|
|
356
|
+
await fs.writeFile(path.join(localDir, item.name), content, 'utf8');
|
|
357
|
+
count++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
else if (item.type === 'dir') {
|
|
361
|
+
const subDir = path.join(localDir, item.name);
|
|
362
|
+
await fs.mkdir(subDir, { recursive: true });
|
|
363
|
+
const subUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${item.path}`;
|
|
364
|
+
const subRes = await fetch(subUrl, {
|
|
365
|
+
headers: { 'User-Agent': 'modular-patchbay/1.0', 'Accept': 'application/vnd.github.v3+json' },
|
|
366
|
+
signal: AbortSignal.timeout(10000),
|
|
367
|
+
});
|
|
368
|
+
if (subRes.ok) {
|
|
369
|
+
const subItems = await subRes.json();
|
|
370
|
+
if (Array.isArray(subItems)) {
|
|
371
|
+
count += await downloadDir(subItems, subDir);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return count;
|
|
377
|
+
};
|
|
378
|
+
const fileCount = await downloadDir(treeData, skillDir);
|
|
379
|
+
console.log(`Installed skill ${skillName}: ${fileCount} files from ${owner}/${repo}/${treePath}`);
|
|
224
380
|
res.json({
|
|
225
381
|
status: 'ok',
|
|
226
|
-
output: `Skill ${skillName} installed to ${skillDir}
|
|
382
|
+
output: `Skill ${skillName} installed to ${skillDir} (${fileCount} files from GitHub)`
|
|
227
383
|
});
|
|
228
384
|
}
|
|
229
385
|
catch (err) {
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Fact } from '../../../src/store/memoryStore.js';
|
|
2
|
+
import type { StorageAdapter } from './storageAdapter.js';
|
|
3
|
+
export declare class HindsightAdapter implements StorageAdapter {
|
|
4
|
+
private readonly client;
|
|
5
|
+
private readonly bank;
|
|
6
|
+
private lastWrite;
|
|
7
|
+
constructor(baseUrl: string, bank?: string);
|
|
8
|
+
initialize(): Promise<void>;
|
|
9
|
+
storeFact(fact: Fact): Promise<void>;
|
|
10
|
+
getFacts(options?: {
|
|
11
|
+
domain?: string;
|
|
12
|
+
limit?: number;
|
|
13
|
+
offset?: number;
|
|
14
|
+
}): Promise<Fact[]>;
|
|
15
|
+
searchFacts(query: string, k?: number): Promise<Array<Fact & {
|
|
16
|
+
score: number;
|
|
17
|
+
}>>;
|
|
18
|
+
deleteFact(_id: string): Promise<void>;
|
|
19
|
+
updateFact(_id: string, _patch: Partial<Fact>): Promise<void>;
|
|
20
|
+
reflect(query: string): Promise<string>;
|
|
21
|
+
getHealth(): Promise<{
|
|
22
|
+
status: string;
|
|
23
|
+
factCount: number;
|
|
24
|
+
lastWrite?: number;
|
|
25
|
+
}>;
|
|
26
|
+
close(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=hindsightAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hindsightAdapter.d.ts","sourceRoot":"","sources":["../../../../server/services/adapters/hindsightAdapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA8B1D,qBAAa,gBAAiB,YAAW,cAAc;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,SAAS,CAAK;gBAEV,OAAO,EAAE,MAAM,EAAE,IAAI,SAAe;IAK1C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAE3B,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAKpC,QAAQ,CAAC,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAMzF,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAM3E,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IACtC,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAE7D,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAIvC,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAS/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAC7B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ModularHindsightClient } from '../hindsightClient.js';
|
|
2
|
+
const DEFAULT_BANK = 'modular-studio';
|
|
3
|
+
const FALLBACK_SCORE = 0.8;
|
|
4
|
+
function toFact(item) {
|
|
5
|
+
return {
|
|
6
|
+
id: item.id,
|
|
7
|
+
content: item.content,
|
|
8
|
+
tags: [],
|
|
9
|
+
type: 'fact',
|
|
10
|
+
timestamp: Date.now(),
|
|
11
|
+
domain: 'shared',
|
|
12
|
+
granularity: 'fact',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function buildFactMeta(fact) {
|
|
16
|
+
const meta = {
|
|
17
|
+
id: fact.id,
|
|
18
|
+
type: fact.type,
|
|
19
|
+
domain: fact.domain,
|
|
20
|
+
granularity: fact.granularity,
|
|
21
|
+
};
|
|
22
|
+
if (fact.ownerAgentId)
|
|
23
|
+
meta.ownerAgentId = fact.ownerAgentId;
|
|
24
|
+
return meta;
|
|
25
|
+
}
|
|
26
|
+
export class HindsightAdapter {
|
|
27
|
+
client;
|
|
28
|
+
bank;
|
|
29
|
+
lastWrite = 0;
|
|
30
|
+
constructor(baseUrl, bank = DEFAULT_BANK) {
|
|
31
|
+
this.client = new ModularHindsightClient(baseUrl);
|
|
32
|
+
this.bank = bank;
|
|
33
|
+
}
|
|
34
|
+
async initialize() { }
|
|
35
|
+
async storeFact(fact) {
|
|
36
|
+
await this.client.retain(this.bank, fact.content, buildFactMeta(fact));
|
|
37
|
+
this.lastWrite = Date.now();
|
|
38
|
+
}
|
|
39
|
+
async getFacts(options) {
|
|
40
|
+
const query = options?.domain ? `domain:${options.domain}` : '*';
|
|
41
|
+
const items = await this.client.recall(this.bank, query, options?.limit ?? 50);
|
|
42
|
+
return items.map(toFact);
|
|
43
|
+
}
|
|
44
|
+
async searchFacts(query, k = 5) {
|
|
45
|
+
const items = await this.client.recall(this.bank, query, k);
|
|
46
|
+
return items.map(item => ({ ...toFact(item), score: FALLBACK_SCORE }));
|
|
47
|
+
}
|
|
48
|
+
// Hindsight is append-only — delete and update are no-ops
|
|
49
|
+
async deleteFact(_id) { }
|
|
50
|
+
async updateFact(_id, _patch) { }
|
|
51
|
+
async reflect(query) {
|
|
52
|
+
return this.client.reflect(this.bank, query);
|
|
53
|
+
}
|
|
54
|
+
async getHealth() {
|
|
55
|
+
const ok = await this.client.healthCheck();
|
|
56
|
+
return {
|
|
57
|
+
status: ok ? 'healthy' : 'unavailable',
|
|
58
|
+
factCount: 0,
|
|
59
|
+
...(this.lastWrite > 0 && { lastWrite: this.lastWrite }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async close() { }
|
|
63
|
+
}
|
|
@@ -23,17 +23,17 @@ export class PostgresAdapter {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
async createTables(client) {
|
|
26
|
-
await client.query(`CREATE TABLE IF NOT EXISTS facts (
|
|
27
|
-
id TEXT PRIMARY KEY,
|
|
28
|
-
content TEXT NOT NULL,
|
|
29
|
-
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
30
|
-
type TEXT NOT NULL,
|
|
31
|
-
timestamp BIGINT NOT NULL,
|
|
32
|
-
domain TEXT NOT NULL,
|
|
33
|
-
granularity TEXT NOT NULL,
|
|
34
|
-
embedding BYTEA,
|
|
35
|
-
owner_agent_id TEXT,
|
|
36
|
-
created_at TIMESTAMP DEFAULT NOW()
|
|
26
|
+
await client.query(`CREATE TABLE IF NOT EXISTS facts (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
content TEXT NOT NULL,
|
|
29
|
+
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
30
|
+
type TEXT NOT NULL,
|
|
31
|
+
timestamp BIGINT NOT NULL,
|
|
32
|
+
domain TEXT NOT NULL,
|
|
33
|
+
granularity TEXT NOT NULL,
|
|
34
|
+
embedding BYTEA,
|
|
35
|
+
owner_agent_id TEXT,
|
|
36
|
+
created_at TIMESTAMP DEFAULT NOW()
|
|
37
37
|
)`);
|
|
38
38
|
// Create indexes for better performance
|
|
39
39
|
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_domain ON facts(domain)');
|
|
@@ -42,7 +42,7 @@ export class PostgresAdapter {
|
|
|
42
42
|
// GIN index for tag queries
|
|
43
43
|
await client.query('CREATE INDEX IF NOT EXISTS idx_facts_tags ON facts USING gin(tags)');
|
|
44
44
|
// Full-text search index
|
|
45
|
-
await client.query(`CREATE INDEX IF NOT EXISTS idx_facts_content_fts
|
|
45
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_facts_content_fts
|
|
46
46
|
ON facts USING gin(to_tsvector('english', content))`);
|
|
47
47
|
}
|
|
48
48
|
async storeFact(fact) {
|
|
@@ -51,18 +51,18 @@ export class PostgresAdapter {
|
|
|
51
51
|
if (!this.pool)
|
|
52
52
|
throw new Error('Pool not initialized');
|
|
53
53
|
const embedding = fact.embedding ? Buffer.from(new Float32Array(fact.embedding).buffer) : null;
|
|
54
|
-
await this.pool.query(`
|
|
55
|
-
INSERT INTO facts (id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id)
|
|
56
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
57
|
-
ON CONFLICT (id) DO UPDATE SET
|
|
58
|
-
content = EXCLUDED.content,
|
|
59
|
-
tags = EXCLUDED.tags,
|
|
60
|
-
type = EXCLUDED.type,
|
|
61
|
-
timestamp = EXCLUDED.timestamp,
|
|
62
|
-
domain = EXCLUDED.domain,
|
|
63
|
-
granularity = EXCLUDED.granularity,
|
|
64
|
-
embedding = EXCLUDED.embedding,
|
|
65
|
-
owner_agent_id = EXCLUDED.owner_agent_id
|
|
54
|
+
await this.pool.query(`
|
|
55
|
+
INSERT INTO facts (id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id)
|
|
56
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
57
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
58
|
+
content = EXCLUDED.content,
|
|
59
|
+
tags = EXCLUDED.tags,
|
|
60
|
+
type = EXCLUDED.type,
|
|
61
|
+
timestamp = EXCLUDED.timestamp,
|
|
62
|
+
domain = EXCLUDED.domain,
|
|
63
|
+
granularity = EXCLUDED.granularity,
|
|
64
|
+
embedding = EXCLUDED.embedding,
|
|
65
|
+
owner_agent_id = EXCLUDED.owner_agent_id
|
|
66
66
|
`, [
|
|
67
67
|
fact.id,
|
|
68
68
|
fact.content,
|
|
@@ -121,12 +121,12 @@ export class PostgresAdapter {
|
|
|
121
121
|
return [];
|
|
122
122
|
// Try PostgreSQL full-text search first
|
|
123
123
|
try {
|
|
124
|
-
const result = await this.pool.query(`
|
|
125
|
-
SELECT *, ts_rank(to_tsvector('english', content), plainto_tsquery('english', $1)) as score
|
|
126
|
-
FROM facts
|
|
127
|
-
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', $1)
|
|
128
|
-
ORDER BY score DESC
|
|
129
|
-
LIMIT $2
|
|
124
|
+
const result = await this.pool.query(`
|
|
125
|
+
SELECT *, ts_rank(to_tsvector('english', content), plainto_tsquery('english', $1)) as score
|
|
126
|
+
FROM facts
|
|
127
|
+
WHERE to_tsvector('english', content) @@ plainto_tsquery('english', $1)
|
|
128
|
+
ORDER BY score DESC
|
|
129
|
+
LIMIT $2
|
|
130
130
|
`, [query, k]);
|
|
131
131
|
if (result.rows.length > 0) {
|
|
132
132
|
return result.rows.map(row => ({
|
|
@@ -25,33 +25,33 @@ export class SqliteAdapter {
|
|
|
25
25
|
createTables() {
|
|
26
26
|
if (!this.db)
|
|
27
27
|
return;
|
|
28
|
-
this.db.run(`CREATE TABLE IF NOT EXISTS facts (
|
|
29
|
-
id TEXT PRIMARY KEY,
|
|
30
|
-
content TEXT NOT NULL,
|
|
31
|
-
tags TEXT NOT NULL,
|
|
32
|
-
type TEXT NOT NULL,
|
|
33
|
-
timestamp INTEGER NOT NULL,
|
|
34
|
-
domain TEXT NOT NULL,
|
|
35
|
-
granularity TEXT NOT NULL,
|
|
36
|
-
embedding BLOB,
|
|
37
|
-
owner_agent_id TEXT
|
|
28
|
+
this.db.run(`CREATE TABLE IF NOT EXISTS facts (
|
|
29
|
+
id TEXT PRIMARY KEY,
|
|
30
|
+
content TEXT NOT NULL,
|
|
31
|
+
tags TEXT NOT NULL,
|
|
32
|
+
type TEXT NOT NULL,
|
|
33
|
+
timestamp INTEGER NOT NULL,
|
|
34
|
+
domain TEXT NOT NULL,
|
|
35
|
+
granularity TEXT NOT NULL,
|
|
36
|
+
embedding BLOB,
|
|
37
|
+
owner_agent_id TEXT
|
|
38
38
|
)`);
|
|
39
39
|
// Create FTS5 table for full-text search
|
|
40
|
-
this.db.run(`CREATE VIRTUAL TABLE IF NOT EXISTS facts_fts USING fts5(
|
|
41
|
-
content,
|
|
42
|
-
content='facts',
|
|
43
|
-
content_rowid='rowid'
|
|
40
|
+
this.db.run(`CREATE VIRTUAL TABLE IF NOT EXISTS facts_fts USING fts5(
|
|
41
|
+
content,
|
|
42
|
+
content='facts',
|
|
43
|
+
content_rowid='rowid'
|
|
44
44
|
)`);
|
|
45
45
|
// Triggers to keep FTS in sync
|
|
46
|
-
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ai AFTER INSERT ON facts BEGIN
|
|
47
|
-
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
46
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ai AFTER INSERT ON facts BEGIN
|
|
47
|
+
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
48
48
|
END`);
|
|
49
|
-
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ad AFTER DELETE ON facts BEGIN
|
|
50
|
-
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
49
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_ad AFTER DELETE ON facts BEGIN
|
|
50
|
+
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
51
51
|
END`);
|
|
52
|
-
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_au AFTER UPDATE ON facts BEGIN
|
|
53
|
-
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
54
|
-
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
52
|
+
this.db.run(`CREATE TRIGGER IF NOT EXISTS facts_au AFTER UPDATE ON facts BEGIN
|
|
53
|
+
INSERT INTO facts_fts(facts_fts, rowid, content) VALUES('delete', old.rowid, old.content);
|
|
54
|
+
INSERT INTO facts_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
55
55
|
END`);
|
|
56
56
|
this.saveDb();
|
|
57
57
|
}
|
|
@@ -69,8 +69,8 @@ export class SqliteAdapter {
|
|
|
69
69
|
if (!this.db)
|
|
70
70
|
throw new Error('Database not initialized');
|
|
71
71
|
const embedding = fact.embedding ? Buffer.from(new Float32Array(fact.embedding).buffer) : null;
|
|
72
|
-
this.db.run(`INSERT OR REPLACE INTO facts (
|
|
73
|
-
id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id
|
|
72
|
+
this.db.run(`INSERT OR REPLACE INTO facts (
|
|
73
|
+
id, content, tags, type, timestamp, domain, granularity, embedding, owner_agent_id
|
|
74
74
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
75
75
|
fact.id,
|
|
76
76
|
fact.content,
|
|
@@ -129,12 +129,12 @@ export class SqliteAdapter {
|
|
|
129
129
|
if (!this.db)
|
|
130
130
|
return [];
|
|
131
131
|
// First try FTS5 full-text search
|
|
132
|
-
const ftsResult = this.db.exec(`
|
|
133
|
-
SELECT facts.*, rank FROM facts_fts
|
|
134
|
-
JOIN facts ON facts.rowid = facts_fts.rowid
|
|
135
|
-
WHERE facts_fts MATCH ?
|
|
136
|
-
ORDER BY rank
|
|
137
|
-
LIMIT ?
|
|
132
|
+
const ftsResult = this.db.exec(`
|
|
133
|
+
SELECT facts.*, rank FROM facts_fts
|
|
134
|
+
JOIN facts ON facts.rowid = facts_fts.rowid
|
|
135
|
+
WHERE facts_fts MATCH ?
|
|
136
|
+
ORDER BY rank
|
|
137
|
+
LIMIT ?
|
|
138
138
|
`, [query, k]);
|
|
139
139
|
if (ftsResult.length > 0) {
|
|
140
140
|
return ftsResult[0].values.map(row => ({
|
|
@@ -35,6 +35,7 @@ export interface AgentVersion {
|
|
|
35
35
|
version: string;
|
|
36
36
|
timestamp: number;
|
|
37
37
|
label?: string;
|
|
38
|
+
changeSummary?: string;
|
|
38
39
|
snapshot: SavedAgentState;
|
|
39
40
|
}
|
|
40
41
|
export interface AgentSummary {
|
|
@@ -44,7 +45,7 @@ export interface AgentSummary {
|
|
|
44
45
|
currentVersion: string;
|
|
45
46
|
}
|
|
46
47
|
export declare function saveAgent(id: string, state: SavedAgentState): void;
|
|
47
|
-
export declare function createAgentVersion(id: string, version: string, label?: string): AgentVersion | null;
|
|
48
|
+
export declare function createAgentVersion(id: string, version: string, label?: string, changeSummary?: string): AgentVersion | null;
|
|
48
49
|
export declare function listAgentVersions(id: string): AgentVersion[];
|
|
49
50
|
export declare function getAgentVersion(id: string, version: string): AgentVersion | null;
|
|
50
51
|
export declare function restoreAgentVersion(id: string, version: string): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agentStore.d.ts","sourceRoot":"","sources":["../../../server/services/agentStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4BH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAQlE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"agentStore.d.ts","sourceRoot":"","sources":["../../../server/services/agentStore.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA4BH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE;QACT,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,EAAE,CAAC;QACf,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IACtC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,eAAe,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,eAAe,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAQlE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAsB3H;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,EAAE,CAiB5D;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAGhF;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAMxE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAavE;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAQ5D;AAED,wBAAgB,UAAU,IAAI,YAAY,EAAE,CAyB3C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CA2B/C"}
|
|
@@ -31,7 +31,7 @@ export function saveAgent(id, state) {
|
|
|
31
31
|
state.savedAt = new Date().toISOString();
|
|
32
32
|
writeFileSync(agentPath(id), JSON.stringify(state, null, 2), 'utf-8');
|
|
33
33
|
}
|
|
34
|
-
export function createAgentVersion(id, version, label) {
|
|
34
|
+
export function createAgentVersion(id, version, label, changeSummary) {
|
|
35
35
|
const current = loadAgent(id);
|
|
36
36
|
if (!current)
|
|
37
37
|
return null;
|
|
@@ -43,6 +43,7 @@ export function createAgentVersion(id, version, label) {
|
|
|
43
43
|
version,
|
|
44
44
|
timestamp,
|
|
45
45
|
label,
|
|
46
|
+
changeSummary,
|
|
46
47
|
snapshot: current,
|
|
47
48
|
};
|
|
48
49
|
const filename = `${timestamp}-v${version.replace(/\./g, '_')}.json`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Correction Detector — identifies when a user is correcting the assistant.
|
|
3
|
+
*
|
|
4
|
+
* Two signal types:
|
|
5
|
+
* direct — regex patterns: "no", "wrong", "not like that", "actually", "instead", "I said"
|
|
6
|
+
* rephrase — consecutive user messages with cosine similarity > 0.85
|
|
7
|
+
*/
|
|
8
|
+
export interface CorrectionSignal {
|
|
9
|
+
type: 'direct' | 'rephrase' | 'override';
|
|
10
|
+
confidence: number;
|
|
11
|
+
userMessage: string;
|
|
12
|
+
previousAssistant: string;
|
|
13
|
+
correctedBehavior: string;
|
|
14
|
+
}
|
|
15
|
+
/** Sync direct-signal detection — regex only, no async needed. */
|
|
16
|
+
export declare function detectCorrection(userMessage: string, previousAssistant: string): CorrectionSignal | null;
|
|
17
|
+
/**
|
|
18
|
+
* Async rephrase detection — cosine similarity > 0.85 between consecutive user messages.
|
|
19
|
+
* Falls back to null if embeddings are unavailable.
|
|
20
|
+
*/
|
|
21
|
+
export declare function detectRephrase(userMessage: string, previousAssistant: string, previousUserMessage: string): Promise<CorrectionSignal | null>;
|
|
22
|
+
//# sourceMappingURL=correctionDetector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"correctionDetector.d.ts","sourceRoot":"","sources":["../../../server/services/correctionDetector.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,QAAQ,GAAG,UAAU,GAAG,UAAU,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAoDD,kEAAkE;AAClE,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,GACxB,gBAAgB,GAAG,IAAI,CAiBzB;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,WAAW,EAAE,MAAM,EACnB,iBAAiB,EAAE,MAAM,EACzB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAelC"}
|