modular-studio 1.0.4 → 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 (129) hide show
  1. package/README.md +122 -41
  2. package/dist/assets/Badge-DrUmDAXz.js +1 -0
  3. package/dist/assets/Input-ndEGQSgx.js +1 -0
  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-CgmwAj_2.js +1 -0
  9. package/dist/assets/TestTab-iJ2vCf9l.js +33 -0
  10. package/dist/assets/ToolsTab-C10Ulm8b.js +1 -0
  11. package/dist/assets/conversationStore-CkfEU2eV.js +1 -0
  12. package/dist/assets/icons-MKpPNvV8.js +1 -0
  13. package/dist/assets/index-B_ip7Amg.css +1 -0
  14. package/dist/assets/index-gBy3427k.js +143 -0
  15. package/dist/assets/{jszip.min-BK6ZQWkj.js → jszip.min-wf-D3Ix_.js} +1 -1
  16. package/dist/assets/markdown-DWF7F0i0.js +29 -0
  17. package/dist/assets/services-CTWXQK6j.js +356 -0
  18. package/dist/assets/stores-CeKWz7ou.js +1 -0
  19. package/dist/assets/vendor-D1h_O76p.js +9 -0
  20. package/dist/index.html +20 -16
  21. package/dist-server/bin/modular-mcp.js +0 -1
  22. package/dist-server/bin/modular-studio.js +0 -1
  23. package/dist-server/server/config.js +0 -1
  24. package/dist-server/server/data/mcp-tokens.json +3 -3
  25. package/dist-server/server/index.d.ts.map +1 -1
  26. package/dist-server/server/index.js +6 -1
  27. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  28. package/dist-server/server/mcp/manager.js +16 -3
  29. package/dist-server/server/mcp/modular-server.js +0 -1
  30. package/dist-server/server/mcp/transport.js +0 -1
  31. package/dist-server/server/routes/agent-sdk.js +0 -1
  32. package/dist-server/server/routes/agents.d.ts +9 -5
  33. package/dist-server/server/routes/agents.d.ts.map +1 -1
  34. package/dist-server/server/routes/agents.js +108 -8
  35. package/dist-server/server/routes/auth-codex.js +0 -1
  36. package/dist-server/server/routes/cache.d.ts +3 -0
  37. package/dist-server/server/routes/cache.d.ts.map +1 -0
  38. package/dist-server/server/routes/cache.js +55 -0
  39. package/dist-server/server/routes/capabilities.js +0 -1
  40. package/dist-server/server/routes/claude-config.js +0 -1
  41. package/dist-server/server/routes/connectors.d.ts.map +1 -1
  42. package/dist-server/server/routes/connectors.js +224 -1
  43. package/dist-server/server/routes/conversations.js +0 -1
  44. package/dist-server/server/routes/embeddings.js +0 -1
  45. package/dist-server/server/routes/health.js +0 -1
  46. package/dist-server/server/routes/knowledge.js +0 -1
  47. package/dist-server/server/routes/lessons.d.ts +3 -0
  48. package/dist-server/server/routes/lessons.d.ts.map +1 -0
  49. package/dist-server/server/routes/lessons.js +46 -0
  50. package/dist-server/server/routes/llm.js +0 -1
  51. package/dist-server/server/routes/mcp-oauth.js +0 -1
  52. package/dist-server/server/routes/mcp.js +0 -1
  53. package/dist-server/server/routes/memory.d.ts +3 -0
  54. package/dist-server/server/routes/memory.d.ts.map +1 -0
  55. package/dist-server/server/routes/memory.js +314 -0
  56. package/dist-server/server/routes/pipeline.js +0 -1
  57. package/dist-server/server/routes/providers.js +0 -1
  58. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  59. package/dist-server/server/routes/qualification.js +341 -75
  60. package/dist-server/server/routes/repo-index.d.ts.map +1 -1
  61. package/dist-server/server/routes/repo-index.js +7 -1
  62. package/dist-server/server/routes/runtime.js +0 -1
  63. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  64. package/dist-server/server/routes/skills-search.js +198 -8
  65. package/dist-server/server/routes/worktrees.js +0 -1
  66. package/dist-server/server/services/__tests__/embeddingService.test.js +0 -1
  67. package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
  68. package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
  69. package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
  70. package/dist-server/server/services/adapters/postgresAdapter.d.ts +29 -0
  71. package/dist-server/server/services/adapters/postgresAdapter.d.ts.map +1 -0
  72. package/dist-server/server/services/adapters/postgresAdapter.js +224 -0
  73. package/dist-server/server/services/adapters/sqliteAdapter.d.ts +28 -0
  74. package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -0
  75. package/dist-server/server/services/adapters/sqliteAdapter.js +219 -0
  76. package/dist-server/server/services/adapters/storageAdapter.d.ts +22 -0
  77. package/dist-server/server/services/adapters/storageAdapter.d.ts.map +1 -0
  78. package/dist-server/server/services/adapters/storageAdapter.js +1 -0
  79. package/dist-server/server/services/agentRunner.js +0 -1
  80. package/dist-server/server/services/agentStore.d.ts +19 -3
  81. package/dist-server/server/services/agentStore.d.ts.map +1 -1
  82. package/dist-server/server/services/agentStore.js +117 -23
  83. package/dist-server/server/services/contentStore.js +0 -1
  84. package/dist-server/server/services/correctionDetector.d.ts +22 -0
  85. package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
  86. package/dist-server/server/services/correctionDetector.js +91 -0
  87. package/dist-server/server/services/embeddingService.d.ts +2 -0
  88. package/dist-server/server/services/embeddingService.d.ts.map +1 -1
  89. package/dist-server/server/services/embeddingService.js +30 -19
  90. package/dist-server/server/services/factExtractor.js +0 -1
  91. package/dist-server/server/services/githubIndexer.js +0 -1
  92. package/dist-server/server/services/hindsightClient.d.ts +15 -0
  93. package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
  94. package/dist-server/server/services/hindsightClient.js +47 -0
  95. package/dist-server/server/services/lessonExtractor.d.ts +19 -0
  96. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
  97. package/dist-server/server/services/lessonExtractor.js +87 -0
  98. package/dist-server/server/services/mcpOAuth.js +0 -1
  99. package/dist-server/server/services/memoryScorer.js +0 -1
  100. package/dist-server/server/services/repoIndexer.js +0 -1
  101. package/dist-server/server/services/responseCache.d.ts +24 -0
  102. package/dist-server/server/services/responseCache.d.ts.map +1 -0
  103. package/dist-server/server/services/responseCache.js +163 -0
  104. package/dist-server/server/services/sqliteStore.d.ts +8 -0
  105. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  106. package/dist-server/server/services/sqliteStore.js +53 -14
  107. package/dist-server/server/services/teamRunner.js +0 -1
  108. package/dist-server/server/services/worktreeManager.js +0 -1
  109. package/dist-server/server/types.d.ts +5 -0
  110. package/dist-server/server/types.d.ts.map +1 -1
  111. package/dist-server/server/types.js +0 -1
  112. package/dist-server/server/utils/pathSecurity.js +0 -1
  113. package/dist-server/src/services/budgetAllocator.js +0 -1
  114. package/dist-server/src/services/contradictionDetector.js +0 -1
  115. package/dist-server/src/services/treeIndexer.js +0 -1
  116. package/dist-server/src/store/knowledgeBase.d.ts +11 -0
  117. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  118. package/dist-server/src/store/knowledgeBase.js +13 -1
  119. package/dist-server/src/store/lessonStore.d.ts +26 -0
  120. package/dist-server/src/store/lessonStore.d.ts.map +1 -0
  121. package/dist-server/src/store/lessonStore.js +64 -0
  122. package/dist-server/src/store/memoryStore.d.ts +118 -0
  123. package/dist-server/src/store/memoryStore.d.ts.map +1 -0
  124. package/dist-server/src/store/memoryStore.js +272 -0
  125. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  126. package/package.json +9 -1
  127. package/dist/assets/graphPopulator-B3rQxb5A.js +0 -1
  128. package/dist/assets/index-BA_J-aHx.js +0 -686
  129. package/dist/assets/index-C7vpqKVZ.css +0 -1
@@ -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,16 +235,159 @@ router.post('/install', async (req, res) => {
188
235
  return;
189
236
  }
190
237
  try {
191
- const args = ['skills', 'add', skillId, '-y'];
192
- if (scope === 'global')
193
- args.push('-g');
194
- const { stdout, stderr } = await exec('npx', args, { timeout: 60000 });
195
- res.json({ status: 'ok', output: stdout + stderr });
238
+ // Try the skills CLI first: `npx skills add <skillId> -y -g`
239
+ try {
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) });
247
+ return;
248
+ }
249
+ catch (cliError) {
250
+ console.log('Skills CLI (npx skills add) failed, trying GitHub fallback:', cliError.message);
251
+ }
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');
256
+ const os = await import('os');
257
+ const path = await import('path');
258
+ const fs = await import('fs/promises');
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);
348
+ await fs.mkdir(skillDir, { recursive: true });
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}`);
380
+ res.json({
381
+ status: 'ok',
382
+ output: `Skill ${skillName} installed to ${skillDir} (${fileCount} files from GitHub)`
383
+ });
196
384
  }
197
385
  catch (err) {
198
386
  const message = err instanceof Error ? err.message : 'Install failed';
199
- res.status(500).json({ error: message });
387
+ console.error('Skills install error:', message);
388
+ res.status(500).json({
389
+ error: `Install failed: ${message}. Please ensure the skills CLI is installed or the skill exists on GitHub.`
390
+ });
200
391
  }
201
392
  });
202
393
  export default router;
203
- //# sourceMappingURL=skills-search.js.map
@@ -67,4 +67,3 @@ router.post('/merge', (req, res) => {
67
67
  }
68
68
  });
69
69
  export default router;
70
- //# sourceMappingURL=worktrees.js.map
@@ -238,4 +238,3 @@ describe('integration (with real model)', () => {
238
238
  // expect(similarity1).toBeGreaterThan(similarity2); // cat-dog more similar than cat-database
239
239
  });
240
240
  });
241
- //# sourceMappingURL=embeddingService.test.js.map
@@ -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
+ }
@@ -0,0 +1,29 @@
1
+ import type { Fact } from '../../../src/store/memoryStore.js';
2
+ import type { StorageAdapter } from './storageAdapter.js';
3
+ export declare class PostgresAdapter implements StorageAdapter {
4
+ private pool;
5
+ private connectionString;
6
+ private lastWrite;
7
+ constructor(connectionString: string);
8
+ initialize(): Promise<void>;
9
+ private createTables;
10
+ storeFact(fact: Fact): Promise<void>;
11
+ getFacts(options?: {
12
+ domain?: string;
13
+ limit?: number;
14
+ offset?: number;
15
+ }): Promise<Fact[]>;
16
+ private rowToFact;
17
+ searchFacts(query: string, k?: number): Promise<Array<Fact & {
18
+ score: number;
19
+ }>>;
20
+ deleteFact(id: string): Promise<void>;
21
+ updateFact(id: string, patch: Partial<Fact>): Promise<void>;
22
+ getHealth(): Promise<{
23
+ status: string;
24
+ factCount: number;
25
+ lastWrite?: number;
26
+ }>;
27
+ close(): Promise<void>;
28
+ }
29
+ //# sourceMappingURL=postgresAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgresAdapter.d.ts","sourceRoot":"","sources":["../../../../server/services/adapters/postgresAdapter.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,qBAAa,eAAgB,YAAW,cAAc;IACpD,OAAO,CAAC,IAAI,CAAqB;IACjC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,SAAS,CAAa;gBAElB,gBAAgB,EAAE,MAAM;IAI9B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBnB,YAAY;IA2BpB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCpC,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;IA4B/F,OAAO,CAAC,SAAS;IAeX,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAsC3E,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAqC3D,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAsB/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B"}
@@ -0,0 +1,224 @@
1
+ import { Pool } from 'pg';
2
+ import { textSimilarity } from '../memoryScorer.js';
3
+ export class PostgresAdapter {
4
+ pool = null;
5
+ connectionString;
6
+ lastWrite = 0;
7
+ constructor(connectionString) {
8
+ this.connectionString = connectionString;
9
+ }
10
+ async initialize() {
11
+ this.pool = new Pool({
12
+ connectionString: this.connectionString,
13
+ max: 10,
14
+ idleTimeoutMillis: 30000,
15
+ connectionTimeoutMillis: 2000,
16
+ });
17
+ const client = await this.pool.connect();
18
+ try {
19
+ await this.createTables(client);
20
+ }
21
+ finally {
22
+ client.release();
23
+ }
24
+ }
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()
37
+ )`);
38
+ // Create indexes for better performance
39
+ await client.query('CREATE INDEX IF NOT EXISTS idx_facts_domain ON facts(domain)');
40
+ await client.query('CREATE INDEX IF NOT EXISTS idx_facts_timestamp ON facts(timestamp)');
41
+ await client.query('CREATE INDEX IF NOT EXISTS idx_facts_type ON facts(type)');
42
+ // GIN index for tag queries
43
+ await client.query('CREATE INDEX IF NOT EXISTS idx_facts_tags ON facts USING gin(tags)');
44
+ // Full-text search index
45
+ await client.query(`CREATE INDEX IF NOT EXISTS idx_facts_content_fts
46
+ ON facts USING gin(to_tsvector('english', content))`);
47
+ }
48
+ async storeFact(fact) {
49
+ if (!this.pool)
50
+ await this.initialize();
51
+ if (!this.pool)
52
+ throw new Error('Pool not initialized');
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
66
+ `, [
67
+ fact.id,
68
+ fact.content,
69
+ JSON.stringify(fact.tags),
70
+ fact.type,
71
+ fact.timestamp,
72
+ fact.domain,
73
+ fact.granularity,
74
+ embedding,
75
+ fact.ownerAgentId || null
76
+ ]);
77
+ this.lastWrite = Date.now();
78
+ }
79
+ async getFacts(options) {
80
+ if (!this.pool)
81
+ await this.initialize();
82
+ if (!this.pool)
83
+ return [];
84
+ let query = 'SELECT * FROM facts';
85
+ const params = [];
86
+ let paramCount = 0;
87
+ if (options?.domain) {
88
+ query += ` WHERE domain = $${++paramCount}`;
89
+ params.push(options.domain);
90
+ }
91
+ query += ' ORDER BY timestamp DESC';
92
+ if (options?.limit) {
93
+ query += ` LIMIT $${++paramCount}`;
94
+ params.push(options.limit);
95
+ if (options.offset) {
96
+ query += ` OFFSET $${++paramCount}`;
97
+ params.push(options.offset);
98
+ }
99
+ }
100
+ const result = await this.pool.query(query, params);
101
+ return result.rows.map(row => this.rowToFact(row));
102
+ }
103
+ rowToFact(row) {
104
+ const embedding = row.embedding ? new Float32Array(new Uint8Array(row.embedding).buffer) : undefined;
105
+ return {
106
+ id: row.id,
107
+ content: row.content,
108
+ tags: JSON.parse(row.tags),
109
+ type: row.type,
110
+ timestamp: parseInt(row.timestamp),
111
+ domain: row.domain,
112
+ granularity: row.granularity,
113
+ embedding: embedding ? Array.from(embedding) : undefined,
114
+ ownerAgentId: row.owner_agent_id || undefined
115
+ };
116
+ }
117
+ async searchFacts(query, k = 5) {
118
+ if (!this.pool)
119
+ await this.initialize();
120
+ if (!this.pool)
121
+ return [];
122
+ // Try PostgreSQL full-text search first
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
130
+ `, [query, k]);
131
+ if (result.rows.length > 0) {
132
+ return result.rows.map(row => ({
133
+ ...this.rowToFact(row),
134
+ score: parseFloat(row.score)
135
+ }));
136
+ }
137
+ }
138
+ catch (error) {
139
+ console.warn('Full-text search failed, falling back to similarity:', error);
140
+ }
141
+ // Fallback to similarity search
142
+ const allFacts = await this.getFacts({ limit: 1000 });
143
+ const scored = allFacts
144
+ .map(fact => ({
145
+ ...fact,
146
+ score: textSimilarity(fact.content, query)
147
+ }))
148
+ .filter(fact => fact.score > 0.1)
149
+ .sort((a, b) => b.score - a.score)
150
+ .slice(0, k);
151
+ return scored;
152
+ }
153
+ async deleteFact(id) {
154
+ if (!this.pool)
155
+ await this.initialize();
156
+ if (!this.pool)
157
+ return;
158
+ await this.pool.query('DELETE FROM facts WHERE id = $1', [id]);
159
+ this.lastWrite = Date.now();
160
+ }
161
+ async updateFact(id, patch) {
162
+ if (!this.pool)
163
+ await this.initialize();
164
+ if (!this.pool)
165
+ return;
166
+ const updates = [];
167
+ const params = [];
168
+ let paramCount = 0;
169
+ if (patch.content !== undefined) {
170
+ updates.push(`content = $${++paramCount}`);
171
+ params.push(patch.content);
172
+ }
173
+ if (patch.tags !== undefined) {
174
+ updates.push(`tags = $${++paramCount}`);
175
+ params.push(JSON.stringify(patch.tags));
176
+ }
177
+ if (patch.type !== undefined) {
178
+ updates.push(`type = $${++paramCount}`);
179
+ params.push(patch.type);
180
+ }
181
+ if (patch.domain !== undefined) {
182
+ updates.push(`domain = $${++paramCount}`);
183
+ params.push(patch.domain);
184
+ }
185
+ if (patch.embedding !== undefined) {
186
+ updates.push(`embedding = $${++paramCount}`);
187
+ const embedding = patch.embedding ? Buffer.from(new Float32Array(patch.embedding).buffer) : null;
188
+ params.push(embedding);
189
+ }
190
+ if (updates.length === 0)
191
+ return;
192
+ params.push(id);
193
+ await this.pool.query(`UPDATE facts SET ${updates.join(', ')} WHERE id = $${++paramCount}`, params);
194
+ this.lastWrite = Date.now();
195
+ }
196
+ async getHealth() {
197
+ if (!this.pool)
198
+ await this.initialize();
199
+ if (!this.pool)
200
+ return { status: 'error', factCount: 0 };
201
+ try {
202
+ const result = await this.pool.query('SELECT COUNT(*) as count FROM facts');
203
+ const factCount = parseInt(result.rows[0].count);
204
+ return {
205
+ status: 'healthy',
206
+ factCount,
207
+ lastWrite: this.lastWrite || undefined
208
+ };
209
+ }
210
+ catch (error) {
211
+ return {
212
+ status: 'error',
213
+ factCount: 0,
214
+ lastWrite: this.lastWrite || undefined
215
+ };
216
+ }
217
+ }
218
+ async close() {
219
+ if (this.pool) {
220
+ await this.pool.end();
221
+ this.pool = null;
222
+ }
223
+ }
224
+ }
@@ -0,0 +1,28 @@
1
+ import type { Fact } from '../../../src/store/memoryStore.js';
2
+ import type { StorageAdapter } from './storageAdapter.js';
3
+ export declare class SqliteAdapter implements StorageAdapter {
4
+ private db;
5
+ private lastWrite;
6
+ initialize(): Promise<void>;
7
+ private createTables;
8
+ private saveDb;
9
+ storeFact(fact: Fact): Promise<void>;
10
+ getFacts(options?: {
11
+ domain?: string;
12
+ limit?: number;
13
+ offset?: number;
14
+ }): Promise<Fact[]>;
15
+ private rowToFact;
16
+ searchFacts(query: string, k?: number): Promise<Array<Fact & {
17
+ score: number;
18
+ }>>;
19
+ deleteFact(id: string): Promise<void>;
20
+ updateFact(id: string, patch: Partial<Fact>): Promise<void>;
21
+ getHealth(): Promise<{
22
+ status: string;
23
+ factCount: number;
24
+ lastWrite?: number;
25
+ }>;
26
+ close(): Promise<void>;
27
+ }
28
+ //# sourceMappingURL=sqliteAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqliteAdapter.d.ts","sourceRoot":"","sources":["../../../../server/services/adapters/sqliteAdapter.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mCAAmC,CAAC;AAC9D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAM1D,qBAAa,aAAc,YAAW,cAAc;IAClD,OAAO,CAAC,EAAE,CAAyB;IACnC,OAAO,CAAC,SAAS,CAAa;IAExB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,YAAY;IAuCpB,OAAO,CAAC,MAAM;IAQR,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBpC,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;IA6B/F,OAAO,CAAC,SAAS;IAeX,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,SAAI,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAkC3E,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrC,UAAU,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAoC3D,SAAS,IAAI,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAc/E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}