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.
Files changed (79) hide show
  1. package/README.md +122 -122
  2. package/dist/assets/{Badge-22Ai0eyi.js → Badge-DrUmDAXz.js} +1 -1
  3. package/dist/assets/{Input-Bgp734xs.js → Input-ndEGQSgx.js} +1 -1
  4. package/dist/assets/KnowledgeTab-CxlC76Rf.js +4 -0
  5. package/dist/assets/MemoryTab-CUScYWs9.js +16 -0
  6. package/dist/assets/QualificationTab-BqnWSQHm.js +1 -0
  7. package/dist/assets/ReviewTab-DKYl6cR9.js +103 -0
  8. package/dist/assets/{Section-DoJrmytO.js → Section-CgmwAj_2.js} +1 -1
  9. package/dist/assets/{TestTab-PDyMF8Fw.js → TestTab-iJ2vCf9l.js} +15 -15
  10. package/dist/assets/ToolsTab-C10Ulm8b.js +1 -0
  11. package/dist/assets/icons-MKpPNvV8.js +1 -0
  12. package/dist/assets/index-B_ip7Amg.css +1 -0
  13. package/dist/assets/index-gBy3427k.js +143 -0
  14. package/dist/assets/services-CTWXQK6j.js +356 -0
  15. package/dist/index.html +18 -18
  16. package/dist-server/server/index.d.ts.map +1 -1
  17. package/dist-server/server/index.js +4 -0
  18. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  19. package/dist-server/server/mcp/manager.js +16 -2
  20. package/dist-server/server/routes/agents.d.ts.map +1 -1
  21. package/dist-server/server/routes/agents.js +27 -0
  22. package/dist-server/server/routes/cache.d.ts +3 -0
  23. package/dist-server/server/routes/cache.d.ts.map +1 -0
  24. package/dist-server/server/routes/cache.js +55 -0
  25. package/dist-server/server/routes/connectors.d.ts.map +1 -1
  26. package/dist-server/server/routes/connectors.js +47 -17
  27. package/dist-server/server/routes/lessons.d.ts +3 -0
  28. package/dist-server/server/routes/lessons.d.ts.map +1 -0
  29. package/dist-server/server/routes/lessons.js +46 -0
  30. package/dist-server/server/routes/memory.d.ts.map +1 -1
  31. package/dist-server/server/routes/memory.js +31 -0
  32. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  33. package/dist-server/server/routes/qualification.js +292 -334
  34. package/dist-server/server/routes/repo-index.d.ts.map +1 -1
  35. package/dist-server/server/routes/repo-index.js +7 -0
  36. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  37. package/dist-server/server/routes/skills-search.js +182 -26
  38. package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
  39. package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
  40. package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
  41. package/dist-server/server/services/adapters/postgresAdapter.js +30 -30
  42. package/dist-server/server/services/adapters/sqliteAdapter.js +29 -29
  43. package/dist-server/server/services/agentStore.d.ts +2 -1
  44. package/dist-server/server/services/agentStore.d.ts.map +1 -1
  45. package/dist-server/server/services/agentStore.js +2 -1
  46. package/dist-server/server/services/correctionDetector.d.ts +22 -0
  47. package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
  48. package/dist-server/server/services/correctionDetector.js +91 -0
  49. package/dist-server/server/services/hindsightClient.d.ts +15 -0
  50. package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
  51. package/dist-server/server/services/hindsightClient.js +47 -0
  52. package/dist-server/server/services/lessonExtractor.d.ts +19 -0
  53. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
  54. package/dist-server/server/services/lessonExtractor.js +87 -0
  55. package/dist-server/server/services/responseCache.d.ts +24 -0
  56. package/dist-server/server/services/responseCache.d.ts.map +1 -0
  57. package/dist-server/server/services/responseCache.js +163 -0
  58. package/dist-server/server/services/sqliteStore.d.ts +8 -0
  59. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  60. package/dist-server/server/services/sqliteStore.js +53 -13
  61. package/dist-server/src/store/knowledgeBase.d.ts +1 -0
  62. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  63. package/dist-server/src/store/lessonStore.d.ts +26 -0
  64. package/dist-server/src/store/lessonStore.d.ts.map +1 -0
  65. package/dist-server/src/store/lessonStore.js +64 -0
  66. package/dist-server/src/store/memoryStore.d.ts +12 -1
  67. package/dist-server/src/store/memoryStore.d.ts.map +1 -1
  68. package/dist-server/src/store/memoryStore.js +9 -0
  69. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  70. package/package.json +105 -104
  71. package/dist/assets/KnowledgeTab-DABxirZh.js +0 -4
  72. package/dist/assets/MemoryTab-DZeYElIT.js +0 -16
  73. package/dist/assets/QualificationTab-Dfpy3J30.js +0 -1
  74. package/dist/assets/ReviewTab-SD8lQuCc.js +0 -103
  75. package/dist/assets/ToolsTab-B83qGCmG.js +0 -1
  76. package/dist/assets/icons-C2EV-le6.js +0 -1
  77. package/dist/assets/index-DkpMAxX7.css +0 -1
  78. package/dist/assets/index-q24ug5Qs.js +0 -143
  79. 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;AAwTD,eAAe,MAAM,CAAC"}
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;AAiRxB,eAAe,MAAM,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 correct skills CLI command first
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
- const { stdout, stderr } = await exec('npx', args, { timeout: 60000 });
197
- res.json({ status: 'ok', output: stdout + stderr });
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
- const skillContent = await skillResponse.text();
214
- // Get user's home directory and create skill directory
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
- const skillsDir = path.join(os.homedir(), '.agents', 'skills');
219
- const skillDir = path.join(skillsDir, skillName);
220
- // Create directories
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
- // Write SKILL.md file
223
- await fs.writeFile(path.join(skillDir, 'SKILL.md'), skillContent, 'utf8');
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} via direct download fallback`
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,CAqBnG;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"}
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"}