modular-studio 1.0.5 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +122 -122
- package/dist/assets/Badge-Bsy2H_p2.js +1 -0
- package/dist/assets/GraphPanel-D4X3faxA.js +47 -0
- package/dist/assets/{Input-Bgp734xs.js → Input-Dyb88Erk.js} +1 -1
- package/dist/assets/KnowledgeTab-BccWz7Np.js +5 -0
- package/dist/assets/MemoryTab-Y_66cE01.js +16 -0
- package/dist/assets/QualificationTab-Dm9dEIpM.js +1 -0
- package/dist/assets/ReviewTab-BrfXSyyf.js +104 -0
- package/dist/assets/{Section-DoJrmytO.js → Section-68XDCFTl.js} +1 -1
- package/dist/assets/TestTab-CLKRT63X.js +42 -0
- package/dist/assets/ToolsTab-xumi9Uds.js +1 -0
- package/dist/assets/icons-CS8RUPBi.js +1 -0
- package/dist/assets/index-B2bm0161.css +1 -0
- package/dist/assets/index-C626nWuA.js +422 -0
- package/dist/assets/services-BDk6yY4o.js +369 -0
- package/dist/index.html +18 -18
- package/dist-server/bin/modular-mcp.js +1 -0
- package/dist-server/server/index.d.ts.map +1 -1
- package/dist-server/server/index.js +34 -0
- package/dist-server/server/mcp/manager.d.ts +3 -0
- package/dist-server/server/mcp/manager.d.ts.map +1 -1
- package/dist-server/server/mcp/manager.js +80 -5
- package/dist-server/server/migrations/index.d.ts +11 -0
- package/dist-server/server/migrations/index.d.ts.map +1 -0
- package/dist-server/server/migrations/index.js +57 -0
- package/dist-server/server/routes/agents.d.ts.map +1 -1
- package/dist-server/server/routes/agents.js +27 -0
- package/dist-server/server/routes/analytics.d.ts +3 -0
- package/dist-server/server/routes/analytics.d.ts.map +1 -0
- package/dist-server/server/routes/analytics.js +24 -0
- package/dist-server/server/routes/cache.d.ts +3 -0
- package/dist-server/server/routes/cache.d.ts.map +1 -0
- package/dist-server/server/routes/cache.js +55 -0
- package/dist-server/server/routes/connectors/airtable.d.ts +7 -0
- package/dist-server/server/routes/connectors/airtable.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/airtable.js +119 -0
- package/dist-server/server/routes/connectors/confluence.d.ts +7 -0
- package/dist-server/server/routes/connectors/confluence.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/confluence.js +176 -0
- package/dist-server/server/routes/connectors/github.d.ts +7 -0
- package/dist-server/server/routes/connectors/github.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/github.js +195 -0
- package/dist-server/server/routes/connectors/gmail.d.ts +7 -0
- package/dist-server/server/routes/connectors/gmail.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/gmail.js +115 -0
- package/dist-server/server/routes/connectors/google-docs.d.ts +10 -0
- package/dist-server/server/routes/connectors/google-docs.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/google-docs.js +165 -0
- package/dist-server/server/routes/connectors/google-drive.d.ts +7 -0
- package/dist-server/server/routes/connectors/google-drive.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/google-drive.js +163 -0
- package/dist-server/server/routes/connectors/google-sheets.d.ts +7 -0
- package/dist-server/server/routes/connectors/google-sheets.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/google-sheets.js +90 -0
- package/dist-server/server/routes/connectors/hubspot.d.ts +7 -0
- package/dist-server/server/routes/connectors/hubspot.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/hubspot.js +134 -0
- package/dist-server/server/routes/connectors/index.d.ts +6 -0
- package/dist-server/server/routes/connectors/index.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/index.js +38 -0
- package/dist-server/server/routes/connectors/jira.d.ts +7 -0
- package/dist-server/server/routes/connectors/jira.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/jira.js +151 -0
- package/dist-server/server/routes/connectors/linear.d.ts +7 -0
- package/dist-server/server/routes/connectors/linear.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/linear.js +154 -0
- package/dist-server/server/routes/connectors/notion.d.ts +10 -0
- package/dist-server/server/routes/connectors/notion.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/notion.js +201 -0
- package/dist-server/server/routes/connectors/plane.d.ts +10 -0
- package/dist-server/server/routes/connectors/plane.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/plane.js +189 -0
- package/dist-server/server/routes/connectors/shared.d.ts +25 -0
- package/dist-server/server/routes/connectors/shared.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/shared.js +202 -0
- package/dist-server/server/routes/connectors/slack.d.ts +7 -0
- package/dist-server/server/routes/connectors/slack.d.ts.map +1 -0
- package/dist-server/server/routes/connectors/slack.js +153 -0
- package/dist-server/server/routes/connectors.d.ts.map +1 -1
- package/dist-server/server/routes/connectors.js +47 -17
- package/dist-server/server/routes/cost.d.ts +3 -0
- package/dist-server/server/routes/cost.d.ts.map +1 -0
- package/dist-server/server/routes/cost.js +113 -0
- package/dist-server/server/routes/graph.d.ts +11 -0
- package/dist-server/server/routes/graph.d.ts.map +1 -0
- package/dist-server/server/routes/graph.js +213 -0
- 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 +160 -0
- package/dist-server/server/routes/llm.d.ts.map +1 -1
- package/dist-server/server/routes/llm.js +85 -18
- package/dist-server/server/routes/memory.d.ts.map +1 -1
- package/dist-server/server/routes/memory.js +31 -0
- package/dist-server/server/routes/metaprompt-v2.d.ts +3 -0
- package/dist-server/server/routes/metaprompt-v2.d.ts.map +1 -0
- package/dist-server/server/routes/metaprompt-v2.js +104 -0
- package/dist-server/server/routes/qualification.d.ts.map +1 -1
- package/dist-server/server/routes/qualification.js +342 -334
- package/dist-server/server/routes/repo-index.d.ts.map +1 -1
- package/dist-server/server/routes/repo-index.js +7 -0
- package/dist-server/server/routes/skills-search.d.ts.map +1 -1
- package/dist-server/server/routes/skills-search.js +192 -26
- package/dist-server/server/routes/tool-analytics.d.ts +3 -0
- package/dist-server/server/routes/tool-analytics.d.ts.map +1 -0
- package/dist-server/server/routes/tool-analytics.js +47 -0
- package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
- package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
- package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
- package/dist-server/server/services/adapters/postgresAdapter.js +30 -30
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts +1 -0
- package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -1
- package/dist-server/server/services/adapters/sqliteAdapter.js +66 -36
- package/dist-server/server/services/agentStore.d.ts +2 -1
- package/dist-server/server/services/agentStore.d.ts.map +1 -1
- package/dist-server/server/services/agentStore.js +2 -1
- package/dist-server/server/services/correctionDetector.d.ts +22 -0
- package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
- package/dist-server/server/services/correctionDetector.js +91 -0
- package/dist-server/server/services/credentialStore.d.ts +10 -0
- package/dist-server/server/services/credentialStore.d.ts.map +1 -0
- package/dist-server/server/services/credentialStore.js +123 -0
- package/dist-server/server/services/hindsightClient.d.ts +15 -0
- package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
- package/dist-server/server/services/hindsightClient.js +48 -0
- package/dist-server/server/services/lessonExtractor.d.ts +21 -0
- package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
- package/dist-server/server/services/lessonExtractor.js +92 -0
- package/dist-server/server/services/repoIndexer.d.ts +7 -1
- package/dist-server/server/services/repoIndexer.d.ts.map +1 -1
- package/dist-server/server/services/repoIndexer.js +295 -94
- 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 +72 -0
- package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
- package/dist-server/server/services/sqliteStore.js +291 -13
- package/dist-server/src/config.d.ts +2 -0
- package/dist-server/src/config.d.ts.map +1 -0
- package/dist-server/src/config.js +3 -0
- package/dist-server/src/graph/db.d.ts +46 -0
- package/dist-server/src/graph/db.d.ts.map +1 -0
- package/dist-server/src/graph/db.js +241 -0
- package/dist-server/src/graph/extractors/code.d.ts +12 -0
- package/dist-server/src/graph/extractors/code.d.ts.map +1 -0
- package/dist-server/src/graph/extractors/code.js +239 -0
- package/dist-server/src/graph/extractors/cross-type.d.ts +16 -0
- package/dist-server/src/graph/extractors/cross-type.d.ts.map +1 -0
- package/dist-server/src/graph/extractors/cross-type.js +67 -0
- package/dist-server/src/graph/extractors/markdown.d.ts +29 -0
- package/dist-server/src/graph/extractors/markdown.d.ts.map +1 -0
- package/dist-server/src/graph/extractors/markdown.js +224 -0
- package/dist-server/src/graph/extractors/yaml.d.ts +15 -0
- package/dist-server/src/graph/extractors/yaml.d.ts.map +1 -0
- package/dist-server/src/graph/extractors/yaml.js +104 -0
- package/dist-server/src/graph/index.d.ts +62 -0
- package/dist-server/src/graph/index.d.ts.map +1 -0
- package/dist-server/src/graph/index.js +67 -0
- package/dist-server/src/graph/packer.d.ts +19 -0
- package/dist-server/src/graph/packer.d.ts.map +1 -0
- package/dist-server/src/graph/packer.js +134 -0
- package/dist-server/src/graph/resolver.d.ts +12 -0
- package/dist-server/src/graph/resolver.d.ts.map +1 -0
- package/dist-server/src/graph/resolver.js +81 -0
- package/dist-server/src/graph/scanner.d.ts +34 -0
- package/dist-server/src/graph/scanner.d.ts.map +1 -0
- package/dist-server/src/graph/scanner.js +252 -0
- package/dist-server/src/graph/traverser.d.ts +17 -0
- package/dist-server/src/graph/traverser.d.ts.map +1 -0
- package/dist-server/src/graph/traverser.js +185 -0
- package/dist-server/src/graph/types.d.ts +117 -0
- package/dist-server/src/graph/types.d.ts.map +1 -0
- package/dist-server/src/graph/types.js +63 -0
- package/dist-server/src/metaprompt/v2/assembler.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/assembler.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/assembler.js +261 -0
- package/dist-server/src/metaprompt/v2/context-strategist.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/context-strategist.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/context-strategist.js +173 -0
- package/dist-server/src/metaprompt/v2/evaluator.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/evaluator.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/evaluator.js +281 -0
- package/dist-server/src/metaprompt/v2/index.d.ts +41 -0
- package/dist-server/src/metaprompt/v2/index.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/index.js +90 -0
- package/dist-server/src/metaprompt/v2/parser.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/parser.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/parser.js +138 -0
- package/dist-server/src/metaprompt/v2/pattern-selector.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/pattern-selector.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/pattern-selector.js +154 -0
- package/dist-server/src/metaprompt/v2/researcher.d.ts +3 -0
- package/dist-server/src/metaprompt/v2/researcher.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/researcher.js +194 -0
- package/dist-server/src/metaprompt/v2/tool-discovery.d.ts +74 -0
- package/dist-server/src/metaprompt/v2/tool-discovery.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/tool-discovery.js +290 -0
- package/dist-server/src/metaprompt/v2/types.d.ts +154 -0
- package/dist-server/src/metaprompt/v2/types.d.ts.map +1 -0
- package/dist-server/src/metaprompt/v2/types.js +2 -0
- package/dist-server/src/services/contradictionDetector.js +1 -1
- package/dist-server/src/services/llmService.d.ts +61 -0
- package/dist-server/src/services/llmService.d.ts.map +1 -0
- package/dist-server/src/services/llmService.js +222 -0
- package/dist-server/src/store/knowledgeBase.d.ts +6 -1
- package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
- package/dist-server/src/store/knowledgeBase.js +0 -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/mcp-registry.d.ts +29 -0
- package/dist-server/src/store/mcp-registry.d.ts.map +1 -0
- package/dist-server/src/store/mcp-registry.js +1303 -0
- package/dist-server/src/store/memoryStore.d.ts +12 -1
- package/dist-server/src/store/memoryStore.d.ts.map +1 -1
- package/dist-server/src/store/memoryStore.js +9 -0
- package/dist-server/src/types/registry.types.d.ts +13 -0
- package/dist-server/src/types/registry.types.d.ts.map +1 -0
- package/dist-server/src/types/registry.types.js +2 -0
- package/dist-server/tsconfig.server.tsbuildinfo +1 -1
- package/package.json +15 -1
- package/scripts/cleanup-worktrees.ps1 +29 -0
- package/dist/assets/Badge-22Ai0eyi.js +0 -1
- package/dist/assets/KnowledgeTab-DABxirZh.js +0 -4
- package/dist/assets/MemoryTab-DZeYElIT.js +0 -16
- package/dist/assets/QualificationTab-Dfpy3J30.js +0 -1
- package/dist/assets/ReviewTab-SD8lQuCc.js +0 -103
- package/dist/assets/TestTab-PDyMF8Fw.js +0 -33
- package/dist/assets/ToolsTab-B83qGCmG.js +0 -1
- package/dist/assets/icons-C2EV-le6.js +0 -1
- package/dist/assets/index-DkpMAxX7.css +0 -1
- package/dist/assets/index-q24ug5Qs.js +0 -143
- package/dist/assets/services-BaKotDf0.js +0 -343
package/dist/index.html
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.min.css" rel="stylesheet" />
|
|
8
|
-
<link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.min.css" rel="stylesheet" />
|
|
9
|
-
<title>MODULAR — Agent Patchbay</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.min.css" rel="stylesheet" />
|
|
8
|
+
<link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.min.css" rel="stylesheet" />
|
|
9
|
+
<title>MODULAR — Agent Patchbay</title>
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-C626nWuA.js"></script>
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendor-D1h_O76p.js">
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/assets/stores-CeKWz7ou.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/services-
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/icons-
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
16
|
-
</head>
|
|
17
|
-
<body style="margin:0;padding:0;height:100vh;overflow:hidden">
|
|
18
|
-
<div id="root" style="height:100%;overflow:hidden"></div>
|
|
19
|
-
</body>
|
|
20
|
-
</html>
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/services-BDk6yY4o.js">
|
|
14
|
+
<link rel="modulepreload" crossorigin href="/assets/icons-CS8RUPBi.js">
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B2bm0161.css">
|
|
16
|
+
</head>
|
|
17
|
+
<body style="margin:0;padding:0;height:100vh;overflow:hidden">
|
|
18
|
+
<div id="root" style="height:100%;overflow:hidden"></div>
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.ts"],"names":[],"mappings":"AAwCA,wBAAgB,SAAS,gDAwHxB;AA+BD,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,sGAwB9C"}
|
|
@@ -27,6 +27,14 @@ import embeddingRoutes from './routes/embeddings.js';
|
|
|
27
27
|
import embeddingService from './services/embeddingService.js';
|
|
28
28
|
import conversationRoutes from './routes/conversations.js';
|
|
29
29
|
import memoryRoutes from './routes/memory.js';
|
|
30
|
+
import cacheRoutes from './routes/cache.js';
|
|
31
|
+
import lessonRoutes from './routes/lessons.js';
|
|
32
|
+
import metapromptV2Routes from './routes/metaprompt-v2.js';
|
|
33
|
+
import graphRoutes from './routes/graph.js';
|
|
34
|
+
import connectorSubRoutes from './routes/connectors/index.js';
|
|
35
|
+
import costRoutes from './routes/cost.js';
|
|
36
|
+
import toolAnalyticsRoutes from './routes/tool-analytics.js';
|
|
37
|
+
import analyticsRoutes from './routes/analytics.js';
|
|
30
38
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
31
39
|
export function createApp() {
|
|
32
40
|
const app = express();
|
|
@@ -40,6 +48,24 @@ export function createApp() {
|
|
|
40
48
|
],
|
|
41
49
|
}));
|
|
42
50
|
app.use(express.json({ limit: '10mb' }));
|
|
51
|
+
// Request logging middleware — correlation IDs + structured logs
|
|
52
|
+
app.use((req, res, next) => {
|
|
53
|
+
const requestId = crypto.randomUUID();
|
|
54
|
+
res.locals['requestId'] = requestId;
|
|
55
|
+
res.setHeader('X-Request-Id', requestId);
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
res.on('finish', () => {
|
|
58
|
+
const duration = Date.now() - start;
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
requestId,
|
|
61
|
+
method: req.method,
|
|
62
|
+
path: req.path,
|
|
63
|
+
status: res.statusCode,
|
|
64
|
+
durationMs: duration,
|
|
65
|
+
}));
|
|
66
|
+
});
|
|
67
|
+
next();
|
|
68
|
+
});
|
|
43
69
|
// Basic security headers (lightweight helmet-like)
|
|
44
70
|
app.use((_req, res, next) => {
|
|
45
71
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
@@ -89,6 +115,14 @@ export function createApp() {
|
|
|
89
115
|
app.use('/api/embeddings', embeddingRoutes);
|
|
90
116
|
app.use('/api/conversations', conversationRoutes);
|
|
91
117
|
app.use('/api/memory', memoryRoutes);
|
|
118
|
+
app.use('/api/cache', cacheRoutes);
|
|
119
|
+
app.use('/api/lessons', lessonRoutes);
|
|
120
|
+
app.use('/api/metaprompt/v2', metapromptV2Routes);
|
|
121
|
+
app.use('/api/graph', graphRoutes);
|
|
122
|
+
app.use('/api/connectors/v2', connectorSubRoutes);
|
|
123
|
+
app.use('/api/cost', costRoutes);
|
|
124
|
+
app.use('/api/tool-analytics', toolAnalyticsRoutes);
|
|
125
|
+
app.use('/api/analytics', analyticsRoutes);
|
|
92
126
|
// API 404 catch-all — log unmatched API routes for debugging
|
|
93
127
|
app.use('/api', (_req, res) => {
|
|
94
128
|
console.warn(`[API 404] ${_req.method} ${_req.originalUrl}`);
|
|
@@ -13,6 +13,8 @@ interface McpConnection {
|
|
|
13
13
|
}>;
|
|
14
14
|
connectedAt: number | null;
|
|
15
15
|
lastError: string | null;
|
|
16
|
+
retryCount: number;
|
|
17
|
+
retryTimer: ReturnType<typeof setTimeout> | null;
|
|
16
18
|
}
|
|
17
19
|
export declare class McpManager {
|
|
18
20
|
private connections;
|
|
@@ -23,6 +25,7 @@ export declare class McpManager {
|
|
|
23
25
|
private normalizeConfig;
|
|
24
26
|
addServer(config: McpServerConfig): void;
|
|
25
27
|
removeServer(id: string): void;
|
|
28
|
+
private scheduleReconnect;
|
|
26
29
|
getServer(id: string): McpConnection | undefined;
|
|
27
30
|
listServers(): Array<McpServerConfig & {
|
|
28
31
|
status: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../server/mcp/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,UAAU,aAAa;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../server/mcp/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,UAAU,aAAa;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAMD,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAoC;IAGvD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAElC;IAGH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEhC;IAEH,OAAO,CAAC,kBAAkB;IAyC1B,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,eAAe;IASvB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAoBxC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAM9B,OAAO,CAAC,iBAAiB;IAyBzB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIhD,WAAW,IAAI,KAAK,CAAC,eAAe,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAS7G,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAkG/E,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BvF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAW1H;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
|
|
@@ -2,6 +2,9 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
|
2
2
|
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
3
3
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
4
4
|
import { getToken } from '../services/mcpOAuth.js';
|
|
5
|
+
const MAX_RETRIES = 5;
|
|
6
|
+
const BACKOFF_BASE_MS = 1_000;
|
|
7
|
+
const BACKOFF_MAX_MS = 30_000;
|
|
5
8
|
export class McpManager {
|
|
6
9
|
connections = new Map();
|
|
7
10
|
// Allowlist of safe MCP executables - SECURITY FIX
|
|
@@ -16,8 +19,22 @@ export class McpManager {
|
|
|
16
19
|
if (!command) {
|
|
17
20
|
throw new Error('MCP command cannot be empty');
|
|
18
21
|
}
|
|
19
|
-
// Extract base command (remove path prefixes)
|
|
20
|
-
const baseCommand = command.split(/[/\\]/).pop()
|
|
22
|
+
// Extract base command (remove path prefixes and .cmd/.exe/.bat extensions)
|
|
23
|
+
const baseCommand = (command.split(/[/\\]/).pop() || '')
|
|
24
|
+
.replace(/\.(cmd|exe|bat)$/i, '')
|
|
25
|
+
.toLowerCase();
|
|
26
|
+
// On Windows, allow `cmd /c <allowed>` pattern — check that the actual program is allowed
|
|
27
|
+
if (baseCommand === 'cmd' && args.length >= 2) {
|
|
28
|
+
const cmdFlag = args[0].toLowerCase();
|
|
29
|
+
if (cmdFlag === '/c' || cmdFlag === '/k') {
|
|
30
|
+
const actualCommand = (args[1].split(/[/\\]/).pop() || '')
|
|
31
|
+
.replace(/\.(cmd|exe|bat)$/i, '')
|
|
32
|
+
.toLowerCase();
|
|
33
|
+
if (this.ALLOWED_MCP_COMMANDS.has(actualCommand)) {
|
|
34
|
+
return; // cmd /c npx ... is fine
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
21
38
|
// Check if command is in allowlist or starts with allowed prefix
|
|
22
39
|
const isAllowed = this.ALLOWED_MCP_COMMANDS.has(baseCommand) ||
|
|
23
40
|
Array.from(this.ALLOWED_MCP_COMMANDS).some(allowed => command.startsWith(allowed));
|
|
@@ -66,11 +83,43 @@ export class McpManager {
|
|
|
66
83
|
tools: [],
|
|
67
84
|
connectedAt: null,
|
|
68
85
|
lastError: null,
|
|
86
|
+
retryCount: 0,
|
|
87
|
+
retryTimer: null,
|
|
69
88
|
});
|
|
70
89
|
}
|
|
71
90
|
removeServer(id) {
|
|
91
|
+
const conn = this.connections.get(id);
|
|
92
|
+
if (conn?.retryTimer)
|
|
93
|
+
clearTimeout(conn.retryTimer);
|
|
72
94
|
this.connections.delete(id);
|
|
73
95
|
}
|
|
96
|
+
scheduleReconnect(id) {
|
|
97
|
+
const conn = this.connections.get(id);
|
|
98
|
+
if (!conn)
|
|
99
|
+
return;
|
|
100
|
+
if (conn.retryCount >= MAX_RETRIES) {
|
|
101
|
+
console.error(`[McpManager] "${id}" exceeded max retries (${MAX_RETRIES}), giving up`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const delayMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, conn.retryCount), BACKOFF_MAX_MS);
|
|
105
|
+
conn.retryCount += 1;
|
|
106
|
+
console.log(`[McpManager] "${id}" will reconnect in ${delayMs}ms (attempt ${conn.retryCount}/${MAX_RETRIES})`);
|
|
107
|
+
conn.retryTimer = setTimeout(async () => {
|
|
108
|
+
const c = this.connections.get(id);
|
|
109
|
+
if (!c || c.status === 'connected')
|
|
110
|
+
return;
|
|
111
|
+
try {
|
|
112
|
+
await this.connect(id);
|
|
113
|
+
c.retryCount = 0; // reset on success
|
|
114
|
+
console.log(`[McpManager] "${id}" reconnected successfully`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
118
|
+
console.warn(`[McpManager] "${id}" reconnect attempt failed: ${msg}`);
|
|
119
|
+
this.scheduleReconnect(id);
|
|
120
|
+
}
|
|
121
|
+
}, delayMs);
|
|
122
|
+
}
|
|
74
123
|
getServer(id) {
|
|
75
124
|
return this.connections.get(id);
|
|
76
125
|
}
|
|
@@ -133,13 +182,15 @@ export class McpManager {
|
|
|
133
182
|
env: { ...process.env, ...conn.config.env },
|
|
134
183
|
});
|
|
135
184
|
client = new Client({ name: 'modular-studio', version: '1.0.0' });
|
|
136
|
-
// Handle process exit
|
|
185
|
+
// Handle process exit — attempt auto-reconnect with exponential backoff
|
|
137
186
|
transport.onclose = () => {
|
|
138
187
|
if (conn.status === 'connected') {
|
|
139
188
|
conn.status = 'error';
|
|
140
189
|
conn.lastError = 'Process exited unexpectedly';
|
|
141
190
|
conn.client = null;
|
|
142
191
|
conn.transport = null;
|
|
192
|
+
console.warn(`[McpManager] "${id}" process exited unexpectedly — scheduling reconnect`);
|
|
193
|
+
this.scheduleReconnect(id);
|
|
143
194
|
}
|
|
144
195
|
};
|
|
145
196
|
await client.connect(transport);
|
|
@@ -147,6 +198,11 @@ export class McpManager {
|
|
|
147
198
|
conn.client = client;
|
|
148
199
|
conn.transport = transport;
|
|
149
200
|
conn.status = 'connected';
|
|
201
|
+
conn.retryCount = 0;
|
|
202
|
+
if (conn.retryTimer) {
|
|
203
|
+
clearTimeout(conn.retryTimer);
|
|
204
|
+
conn.retryTimer = null;
|
|
205
|
+
}
|
|
150
206
|
conn.tools = tools.map((t) => ({
|
|
151
207
|
name: t.name,
|
|
152
208
|
description: t.description,
|
|
@@ -168,13 +224,32 @@ export class McpManager {
|
|
|
168
224
|
if (!conn.client || conn.status !== 'connected') {
|
|
169
225
|
throw new Error(`MCP server "${id}" is not connected`);
|
|
170
226
|
}
|
|
171
|
-
const
|
|
172
|
-
|
|
227
|
+
const TIMEOUT_MS = 30_000;
|
|
228
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP tool "${toolName}" on server "${id}" timed out after 30s`)), TIMEOUT_MS));
|
|
229
|
+
try {
|
|
230
|
+
const result = await Promise.race([
|
|
231
|
+
conn.client.callTool({ name: toolName, arguments: args }),
|
|
232
|
+
timeoutPromise,
|
|
233
|
+
]);
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
catch (err) {
|
|
237
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
238
|
+
if (msg.includes('timed out')) {
|
|
239
|
+
console.warn(`[McpManager] ${msg}`);
|
|
240
|
+
}
|
|
241
|
+
throw err;
|
|
242
|
+
}
|
|
173
243
|
}
|
|
174
244
|
async disconnect(id) {
|
|
175
245
|
const conn = this.connections.get(id);
|
|
176
246
|
if (!conn)
|
|
177
247
|
throw new Error(`MCP server "${id}" not found`);
|
|
248
|
+
if (conn.retryTimer) {
|
|
249
|
+
clearTimeout(conn.retryTimer);
|
|
250
|
+
conn.retryTimer = null;
|
|
251
|
+
}
|
|
252
|
+
conn.retryCount = 0;
|
|
178
253
|
if (conn.client) {
|
|
179
254
|
try {
|
|
180
255
|
await conn.client.close();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database migration system for Modular Studio.
|
|
3
|
+
* Tracks schema version and runs pending migrations on startup.
|
|
4
|
+
*/
|
|
5
|
+
import type { Database } from 'sql.js';
|
|
6
|
+
/**
|
|
7
|
+
* Run all pending migrations on the given database.
|
|
8
|
+
* Called once during server startup after initDb().
|
|
9
|
+
*/
|
|
10
|
+
export declare function runMigrations(db: Database): void;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../server/migrations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAyCvC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBhD"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** All migrations ordered by version number */
|
|
2
|
+
const MIGRATIONS = [
|
|
3
|
+
{
|
|
4
|
+
version: 1,
|
|
5
|
+
description: 'Initial schema (baseline)',
|
|
6
|
+
// Version 1 is the baseline — tables are created by sqliteStore.ts initDb().
|
|
7
|
+
// This migration just marks the schema as versioned.
|
|
8
|
+
up: (_db) => { },
|
|
9
|
+
},
|
|
10
|
+
];
|
|
11
|
+
const CURRENT_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
12
|
+
/** Ensure the schema_version tracking table exists */
|
|
13
|
+
function ensureVersionTable(db) {
|
|
14
|
+
db.run(`CREATE TABLE IF NOT EXISTS schema_version (
|
|
15
|
+
version INTEGER NOT NULL,
|
|
16
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
17
|
+
)`);
|
|
18
|
+
}
|
|
19
|
+
/** Get the currently applied schema version (0 if none) */
|
|
20
|
+
function getCurrentVersion(db) {
|
|
21
|
+
try {
|
|
22
|
+
const result = db.exec(`SELECT MAX(version) as v FROM schema_version`);
|
|
23
|
+
if (result.length === 0 || result[0].values.length === 0)
|
|
24
|
+
return 0;
|
|
25
|
+
const v = result[0].values[0][0];
|
|
26
|
+
return typeof v === 'number' ? v : 0;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Run all pending migrations on the given database.
|
|
34
|
+
* Called once during server startup after initDb().
|
|
35
|
+
*/
|
|
36
|
+
export function runMigrations(db) {
|
|
37
|
+
ensureVersionTable(db);
|
|
38
|
+
const currentVersion = getCurrentVersion(db);
|
|
39
|
+
if (currentVersion >= CURRENT_SCHEMA_VERSION) {
|
|
40
|
+
console.log(`[DB] Schema up-to-date (v${currentVersion})`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const pending = MIGRATIONS.filter((m) => m.version > currentVersion);
|
|
44
|
+
console.log(`[DB] Running ${pending.length} migration(s) (v${currentVersion} → v${CURRENT_SCHEMA_VERSION})`);
|
|
45
|
+
for (const migration of pending) {
|
|
46
|
+
try {
|
|
47
|
+
migration.up(db);
|
|
48
|
+
db.run(`INSERT INTO schema_version (version) VALUES (?)`, [migration.version]);
|
|
49
|
+
console.log(`[DB] Applied migration v${migration.version}: ${migration.description}`);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
53
|
+
console.error(`[DB] Migration v${migration.version} failed: ${msg}`);
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../server/routes/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,QAAA,MAAM,MAAM,4CAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../server/routes/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0JxB,eAAe,MAAM,CAAC"}
|
|
@@ -73,6 +73,32 @@ router.put('/:id', (req, res) => {
|
|
|
73
73
|
res.status(500).json({ status: 'error', error: err.message });
|
|
74
74
|
}
|
|
75
75
|
});
|
|
76
|
+
// Explicit save — always creates a version snapshot then persists new state
|
|
77
|
+
router.post('/:id/save', (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const { state, label, changeSummary } = req.body;
|
|
80
|
+
if (!state || typeof state !== 'object') {
|
|
81
|
+
res.status(400).json({ status: 'error', error: 'Invalid body' });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const existing = loadAgent(req.params.id);
|
|
85
|
+
const versionEntry = existing
|
|
86
|
+
? createAgentVersion(req.params.id, existing.version, label, changeSummary)
|
|
87
|
+
: null;
|
|
88
|
+
saveAgent(req.params.id, state);
|
|
89
|
+
res.json({
|
|
90
|
+
status: 'ok',
|
|
91
|
+
data: {
|
|
92
|
+
id: req.params.id,
|
|
93
|
+
version: state.version,
|
|
94
|
+
versionId: versionEntry?.id ?? null,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
76
102
|
// Delete agent
|
|
77
103
|
router.delete('/:id', (req, res) => {
|
|
78
104
|
try {
|
|
@@ -96,6 +122,7 @@ router.get('/:id/versions', (req, res) => {
|
|
|
96
122
|
version: v.version,
|
|
97
123
|
timestamp: v.timestamp,
|
|
98
124
|
label: v.label,
|
|
125
|
+
changeSummary: v.changeSummary,
|
|
99
126
|
}));
|
|
100
127
|
res.json({ status: 'ok', data: formatted });
|
|
101
128
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../server/routes/analytics.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2BxB,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { trackUsageEvent, getUsageStats } from '../services/sqliteStore.js';
|
|
3
|
+
const router = Router();
|
|
4
|
+
/** POST /api/analytics/track — record a usage event */
|
|
5
|
+
router.post('/track', async (req, res) => {
|
|
6
|
+
const { event, agentId, metadata } = req.body;
|
|
7
|
+
if (!event) {
|
|
8
|
+
res.status(400).json({ status: 'error', error: 'event is required' });
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
await trackUsageEvent(event, agentId, metadata);
|
|
12
|
+
res.json({ status: 'ok' });
|
|
13
|
+
});
|
|
14
|
+
/** GET /api/analytics/stats — get usage summary */
|
|
15
|
+
router.get('/stats', async (_req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const stats = await getUsageStats();
|
|
18
|
+
res.json({ status: 'ok', data: stats });
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : String(err) });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../server/routes/cache.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqDxB,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { checkCache, storeResponse, getCacheStats, purgeCache, evictExpired, evictLRU } from '../services/responseCache.js';
|
|
3
|
+
const router = Router();
|
|
4
|
+
// GET /api/cache/stats
|
|
5
|
+
router.get('/stats', async (_req, res) => {
|
|
6
|
+
try {
|
|
7
|
+
const stats = await getCacheStats();
|
|
8
|
+
res.json({ status: 'success', ...stats });
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
// DELETE /api/cache/purge?agentId=
|
|
15
|
+
router.delete('/purge', async (req, res) => {
|
|
16
|
+
try {
|
|
17
|
+
const agentId = typeof req.query['agentId'] === 'string' ? req.query['agentId'] : undefined;
|
|
18
|
+
await purgeCache(agentId);
|
|
19
|
+
res.json({ status: 'success' });
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
// POST /api/cache/check — { query, agentId, model, systemPromptHash, ttl? }
|
|
26
|
+
router.post('/check', async (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const { query, agentId, model, systemPromptHash, ttl } = req.body;
|
|
29
|
+
if (!query || !agentId || !model || !systemPromptHash) {
|
|
30
|
+
return res.status(400).json({ status: 'error', error: 'Missing required fields' });
|
|
31
|
+
}
|
|
32
|
+
const hit = await checkCache(query, agentId, model, systemPromptHash, ttl ?? 3600);
|
|
33
|
+
res.json({ status: 'success', hit: hit !== null, cached: hit });
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// POST /api/cache/store — { query, response, agentId, model, systemPromptHash, ttl? }
|
|
40
|
+
router.post('/store', async (req, res) => {
|
|
41
|
+
try {
|
|
42
|
+
const { query, response, agentId, model, systemPromptHash, ttl } = req.body;
|
|
43
|
+
if (!query || !response || !agentId || !model || !systemPromptHash) {
|
|
44
|
+
return res.status(400).json({ status: 'error', error: 'Missing required fields' });
|
|
45
|
+
}
|
|
46
|
+
await storeResponse(query, response, agentId, model, systemPromptHash, ttl ?? 3600);
|
|
47
|
+
await evictExpired();
|
|
48
|
+
await evictLRU(1000);
|
|
49
|
+
res.json({ status: 'success' });
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"airtable.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/airtable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2GxB,eAAe,MAAM,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Airtable Connector — Bases + Tables
|
|
3
|
+
* Issue #95
|
|
4
|
+
*/
|
|
5
|
+
import { Router } from 'express';
|
|
6
|
+
import { rateLimitedFetch, fetchPaginated, toMarkdownTable, connectorError, getApiKey } from './shared.js';
|
|
7
|
+
const router = Router();
|
|
8
|
+
const sessionKeys = new Map();
|
|
9
|
+
const AT_API = 'https://api.airtable.com/v0';
|
|
10
|
+
function atHeaders(token) {
|
|
11
|
+
return { Authorization: `Bearer ${token}` };
|
|
12
|
+
}
|
|
13
|
+
// ── Test ──
|
|
14
|
+
router.post('/test', async (req, res) => {
|
|
15
|
+
const { apiKey } = req.body;
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
res.status(400).json({ status: 'error', error: 'Missing personal access token' });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const resp = await rateLimitedFetch('https://api.airtable.com/v0/meta/whoami', { headers: atHeaders(apiKey) });
|
|
22
|
+
if (!resp.ok) {
|
|
23
|
+
res.status(401).json({ status: 'error', error: 'Invalid Airtable token' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const user = await resp.json();
|
|
27
|
+
sessionKeys.set('airtable', apiKey);
|
|
28
|
+
res.json({ status: 'ok', data: { userId: user.id } });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
connectorError(res, 'Airtable', err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
// ── List Bases ──
|
|
35
|
+
router.post('/bases', async (req, res) => {
|
|
36
|
+
const body = req.body;
|
|
37
|
+
const token = getApiKey('airtable', body, sessionKeys);
|
|
38
|
+
if (!token) {
|
|
39
|
+
res.status(401).json({ status: 'error', error: 'No Airtable token.' });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const resp = await rateLimitedFetch('https://api.airtable.com/v0/meta/bases', { headers: atHeaders(token) });
|
|
44
|
+
if (!resp.ok)
|
|
45
|
+
throw new Error(`Airtable API ${resp.status}`);
|
|
46
|
+
const data = await resp.json();
|
|
47
|
+
res.json({ status: 'ok', data: data.bases });
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
connectorError(res, 'Airtable', err);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
// ── Fetch Table Records ──
|
|
54
|
+
router.post('/fetch', async (req, res) => {
|
|
55
|
+
const body = req.body;
|
|
56
|
+
const token = getApiKey('airtable', body, sessionKeys);
|
|
57
|
+
if (!token) {
|
|
58
|
+
res.status(401).json({ status: 'error', error: 'No Airtable token.' });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const baseId = body.baseId;
|
|
62
|
+
const tableId = body.tableId;
|
|
63
|
+
const viewName = body.viewName;
|
|
64
|
+
if (!baseId || !tableId) {
|
|
65
|
+
res.status(400).json({ status: 'error', error: 'baseId and tableId required' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const items = await fetchPaginated({
|
|
70
|
+
maxPages: 10,
|
|
71
|
+
maxItems: 500,
|
|
72
|
+
fetchPage: async (cursor) => {
|
|
73
|
+
const params = new URLSearchParams({ pageSize: '100' });
|
|
74
|
+
if (viewName)
|
|
75
|
+
params.set('view', viewName);
|
|
76
|
+
if (cursor)
|
|
77
|
+
params.set('offset', cursor);
|
|
78
|
+
const resp = await rateLimitedFetch(`${AT_API}/${baseId}/${encodeURIComponent(tableId)}?${params}`, { headers: atHeaders(token) });
|
|
79
|
+
if (!resp.ok)
|
|
80
|
+
throw new Error(`Airtable API ${resp.status}`);
|
|
81
|
+
const data = await resp.json();
|
|
82
|
+
return {
|
|
83
|
+
items: data.records.map((r) => ({
|
|
84
|
+
id: r.id,
|
|
85
|
+
fields: r.fields ?? {},
|
|
86
|
+
createdTime: r.createdTime,
|
|
87
|
+
})),
|
|
88
|
+
nextCursor: data.offset,
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Build markdown table from fields
|
|
93
|
+
const allKeys = new Set();
|
|
94
|
+
for (const item of items) {
|
|
95
|
+
for (const key of Object.keys(item.fields))
|
|
96
|
+
allKeys.add(key);
|
|
97
|
+
}
|
|
98
|
+
const headers = Array.from(allKeys);
|
|
99
|
+
const rows = items.map((item) => headers.map(h => {
|
|
100
|
+
const val = item.fields[h];
|
|
101
|
+
if (val === null || val === undefined)
|
|
102
|
+
return '';
|
|
103
|
+
if (Array.isArray(val))
|
|
104
|
+
return val.map(v => typeof v === 'object' ? JSON.stringify(v) : String(v)).join(', ');
|
|
105
|
+
if (typeof val === 'object')
|
|
106
|
+
return JSON.stringify(val);
|
|
107
|
+
return String(val);
|
|
108
|
+
}));
|
|
109
|
+
const markdown = `# ${tableId} (${items.length} records)\n\n` + toMarkdownTable(headers, rows);
|
|
110
|
+
res.json({
|
|
111
|
+
status: 'ok',
|
|
112
|
+
data: { items, markdown, count: items.length, tokens: Math.ceil(markdown.length / 4) },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
connectorError(res, 'Airtable', err);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
export default router;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"confluence.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/confluence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqLxB,eAAe,MAAM,CAAC"}
|