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.
- package/README.md +122 -41
- package/dist/assets/Badge-DrUmDAXz.js +1 -0
- package/dist/assets/Input-ndEGQSgx.js +1 -0
- 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-CgmwAj_2.js +1 -0
- package/dist/assets/TestTab-iJ2vCf9l.js +33 -0
- package/dist/assets/ToolsTab-C10Ulm8b.js +1 -0
- package/dist/assets/conversationStore-CkfEU2eV.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/{jszip.min-BK6ZQWkj.js → jszip.min-wf-D3Ix_.js} +1 -1
- package/dist/assets/markdown-DWF7F0i0.js +29 -0
- package/dist/assets/services-CTWXQK6j.js +356 -0
- package/dist/assets/stores-CeKWz7ou.js +1 -0
- package/dist/assets/vendor-D1h_O76p.js +9 -0
- package/dist/index.html +20 -16
- package/dist-server/bin/modular-mcp.js +0 -1
- package/dist-server/bin/modular-studio.js +0 -1
- package/dist-server/server/config.js +0 -1
- package/dist-server/server/data/mcp-tokens.json +3 -3
- package/dist-server/server/index.d.ts.map +1 -1
- package/dist-server/server/index.js +6 -1
- package/dist-server/server/mcp/manager.d.ts.map +1 -1
- package/dist-server/server/mcp/manager.js +16 -3
- package/dist-server/server/mcp/modular-server.js +0 -1
- package/dist-server/server/mcp/transport.js +0 -1
- package/dist-server/server/routes/agent-sdk.js +0 -1
- package/dist-server/server/routes/agents.d.ts +9 -5
- package/dist-server/server/routes/agents.d.ts.map +1 -1
- package/dist-server/server/routes/agents.js +108 -8
- package/dist-server/server/routes/auth-codex.js +0 -1
- 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/capabilities.js +0 -1
- package/dist-server/server/routes/claude-config.js +0 -1
- package/dist-server/server/routes/connectors.d.ts.map +1 -1
- package/dist-server/server/routes/connectors.js +224 -1
- package/dist-server/server/routes/conversations.js +0 -1
- package/dist-server/server/routes/embeddings.js +0 -1
- package/dist-server/server/routes/health.js +0 -1
- package/dist-server/server/routes/knowledge.js +0 -1
- 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/llm.js +0 -1
- package/dist-server/server/routes/mcp-oauth.js +0 -1
- package/dist-server/server/routes/mcp.js +0 -1
- package/dist-server/server/routes/memory.d.ts +3 -0
- package/dist-server/server/routes/memory.d.ts.map +1 -0
- package/dist-server/server/routes/memory.js +314 -0
- package/dist-server/server/routes/pipeline.js +0 -1
- package/dist-server/server/routes/providers.js +0 -1
- package/dist-server/server/routes/qualification.d.ts.map +1 -1
- package/dist-server/server/routes/qualification.js +341 -75
- package/dist-server/server/routes/repo-index.d.ts.map +1 -1
- package/dist-server/server/routes/repo-index.js +7 -1
- package/dist-server/server/routes/runtime.js +0 -1
- package/dist-server/server/routes/skills-search.d.ts.map +1 -1
- package/dist-server/server/routes/skills-search.js +198 -8
- package/dist-server/server/routes/worktrees.js +0 -1
- package/dist-server/server/services/__tests__/embeddingService.test.js +0 -1
- 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.d.ts +29 -0
- package/dist-server/server/services/adapters/postgresAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/postgresAdapter.js +224 -0
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts +28 -0
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/sqliteAdapter.js +219 -0
- package/dist-server/server/services/adapters/storageAdapter.d.ts +22 -0
- package/dist-server/server/services/adapters/storageAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/storageAdapter.js +1 -0
- package/dist-server/server/services/agentRunner.js +0 -1
- package/dist-server/server/services/agentStore.d.ts +19 -3
- package/dist-server/server/services/agentStore.d.ts.map +1 -1
- package/dist-server/server/services/agentStore.js +117 -23
- package/dist-server/server/services/contentStore.js +0 -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/embeddingService.d.ts +2 -0
- package/dist-server/server/services/embeddingService.d.ts.map +1 -1
- package/dist-server/server/services/embeddingService.js +30 -19
- package/dist-server/server/services/factExtractor.js +0 -1
- package/dist-server/server/services/githubIndexer.js +0 -1
- 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/mcpOAuth.js +0 -1
- package/dist-server/server/services/memoryScorer.js +0 -1
- package/dist-server/server/services/repoIndexer.js +0 -1
- 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 -14
- package/dist-server/server/services/teamRunner.js +0 -1
- package/dist-server/server/services/worktreeManager.js +0 -1
- package/dist-server/server/types.d.ts +5 -0
- package/dist-server/server/types.d.ts.map +1 -1
- package/dist-server/server/types.js +0 -1
- package/dist-server/server/utils/pathSecurity.js +0 -1
- package/dist-server/src/services/budgetAllocator.js +0 -1
- package/dist-server/src/services/contradictionDetector.js +0 -1
- package/dist-server/src/services/treeIndexer.js +0 -1
- package/dist-server/src/store/knowledgeBase.d.ts +11 -0
- package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
- package/dist-server/src/store/knowledgeBase.js +13 -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 +118 -0
- package/dist-server/src/store/memoryStore.d.ts.map +1 -0
- package/dist-server/src/store/memoryStore.js +272 -0
- package/dist-server/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +9 -1
- package/dist/assets/graphPopulator-B3rQxb5A.js +0 -1
- package/dist/assets/index-BA_J-aHx.js +0 -686
- 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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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"}
|