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
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"https://mcp.notion.com/mcp": {
|
|
3
|
-
"access_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:
|
|
4
|
-
"refresh_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:
|
|
3
|
+
"access_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:BaO5Cro0sFGkGrPd:QSeNWAA58uvImu3ClN15vEJ1b8D212Vk",
|
|
4
|
+
"refresh_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:BaO5Cro0sFGkGrPd:OMTud6jaTvrQnDZtt31i9bIFxBb2StTR",
|
|
5
5
|
"token_type": "bearer",
|
|
6
6
|
"scope": "",
|
|
7
|
-
"expires_at":
|
|
7
|
+
"expires_at": 1773338149330
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -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":"AAkCA,wBAAgB,SAAS,gDA+FxB;AA+BD,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,sGAwB9C"}
|
|
@@ -26,6 +26,9 @@ import pipelineRoutes from './routes/pipeline.js';
|
|
|
26
26
|
import embeddingRoutes from './routes/embeddings.js';
|
|
27
27
|
import embeddingService from './services/embeddingService.js';
|
|
28
28
|
import conversationRoutes from './routes/conversations.js';
|
|
29
|
+
import memoryRoutes from './routes/memory.js';
|
|
30
|
+
import cacheRoutes from './routes/cache.js';
|
|
31
|
+
import lessonRoutes from './routes/lessons.js';
|
|
29
32
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
33
|
export function createApp() {
|
|
31
34
|
const app = express();
|
|
@@ -87,6 +90,9 @@ export function createApp() {
|
|
|
87
90
|
app.use('/api/pipeline', pipelineRoutes);
|
|
88
91
|
app.use('/api/embeddings', embeddingRoutes);
|
|
89
92
|
app.use('/api/conversations', conversationRoutes);
|
|
93
|
+
app.use('/api/memory', memoryRoutes);
|
|
94
|
+
app.use('/api/cache', cacheRoutes);
|
|
95
|
+
app.use('/api/lessons', lessonRoutes);
|
|
90
96
|
// API 404 catch-all — log unmatched API routes for debugging
|
|
91
97
|
app.use('/api', (_req, res) => {
|
|
92
98
|
console.warn(`[API 404] ${_req.method} ${_req.originalUrl}`);
|
|
@@ -185,4 +191,3 @@ if (isMainModule && !globalThis.__modularStudioStarted) {
|
|
|
185
191
|
process.on('SIGINT', () => { clearInterval(keepAlive); server.close(); process.exit(0); });
|
|
186
192
|
process.on('SIGTERM', () => { clearInterval(keepAlive); server.close(); process.exit(0); });
|
|
187
193
|
}
|
|
188
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -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;CAC1B;AAED,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;
|
|
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;CAC1B;AAED,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;IAkBxC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI9B,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;IA8F/E,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,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"}
|
|
@@ -16,8 +16,22 @@ export class McpManager {
|
|
|
16
16
|
if (!command) {
|
|
17
17
|
throw new Error('MCP command cannot be empty');
|
|
18
18
|
}
|
|
19
|
-
// Extract base command (remove path prefixes)
|
|
20
|
-
const baseCommand = command.split(/[/\\]/).pop()
|
|
19
|
+
// Extract base command (remove path prefixes and .cmd/.exe/.bat extensions)
|
|
20
|
+
const baseCommand = (command.split(/[/\\]/).pop() || '')
|
|
21
|
+
.replace(/\.(cmd|exe|bat)$/i, '')
|
|
22
|
+
.toLowerCase();
|
|
23
|
+
// On Windows, allow `cmd /c <allowed>` pattern — check that the actual program is allowed
|
|
24
|
+
if (baseCommand === 'cmd' && args.length >= 2) {
|
|
25
|
+
const cmdFlag = args[0].toLowerCase();
|
|
26
|
+
if (cmdFlag === '/c' || cmdFlag === '/k') {
|
|
27
|
+
const actualCommand = (args[1].split(/[/\\]/).pop() || '')
|
|
28
|
+
.replace(/\.(cmd|exe|bat)$/i, '')
|
|
29
|
+
.toLowerCase();
|
|
30
|
+
if (this.ALLOWED_MCP_COMMANDS.has(actualCommand)) {
|
|
31
|
+
return; // cmd /c npx ... is fine
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
21
35
|
// Check if command is in allowlist or starts with allowed prefix
|
|
22
36
|
const isAllowed = this.ALLOWED_MCP_COMMANDS.has(baseCommand) ||
|
|
23
37
|
Array.from(this.ALLOWED_MCP_COMMANDS).some(allowed => command.startsWith(allowed));
|
|
@@ -200,4 +214,3 @@ export class McpManager {
|
|
|
200
214
|
}
|
|
201
215
|
}
|
|
202
216
|
export const mcpManager = new McpManager();
|
|
203
|
-
//# sourceMappingURL=manager.js.map
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent CRUD Routes
|
|
3
|
-
* GET /api/agents
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Agent CRUD Routes with Versioning
|
|
3
|
+
* GET /api/agents — list all (summary only)
|
|
4
|
+
* POST /api/agents — create new agent (returns agentId)
|
|
5
|
+
* GET /api/agents/:id — full latest state
|
|
6
|
+
* PUT /api/agents/:id — save/update (creates new version)
|
|
7
|
+
* DELETE /api/agents/:id — delete
|
|
8
|
+
* GET /api/agents/:id/versions — list all versions
|
|
9
|
+
* POST /api/agents/:id/versions/:version/restore — restore to specific version
|
|
10
|
+
* DELETE /api/agents/:id/versions/:version — delete a version
|
|
7
11
|
*/
|
|
8
12
|
declare const router: import("express-serve-static-core").Router;
|
|
9
13
|
export default router;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../server/routes/agents.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent CRUD Routes
|
|
3
|
-
* GET /api/agents
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Agent CRUD Routes with Versioning
|
|
3
|
+
* GET /api/agents — list all (summary only)
|
|
4
|
+
* POST /api/agents — create new agent (returns agentId)
|
|
5
|
+
* GET /api/agents/:id — full latest state
|
|
6
|
+
* PUT /api/agents/:id — save/update (creates new version)
|
|
7
|
+
* DELETE /api/agents/:id — delete
|
|
8
|
+
* GET /api/agents/:id/versions — list all versions
|
|
9
|
+
* POST /api/agents/:id/versions/:version/restore — restore to specific version
|
|
10
|
+
* DELETE /api/agents/:id/versions/:version — delete a version
|
|
7
11
|
*/
|
|
8
12
|
import { Router } from 'express';
|
|
9
|
-
import { saveAgent, loadAgent, listAgents, deleteAgent } from '../services/agentStore.js';
|
|
13
|
+
import { saveAgent, loadAgent, listAgents, deleteAgent, createAgentVersion, listAgentVersions, restoreAgentVersion, deleteAgentVersion } from '../services/agentStore.js';
|
|
10
14
|
const router = Router();
|
|
15
|
+
// List all agents
|
|
11
16
|
router.get('/', (_req, res) => {
|
|
12
17
|
try {
|
|
13
18
|
const agents = listAgents();
|
|
@@ -17,6 +22,24 @@ router.get('/', (_req, res) => {
|
|
|
17
22
|
res.status(500).json({ status: 'error', error: err.message });
|
|
18
23
|
}
|
|
19
24
|
});
|
|
25
|
+
// Create new agent
|
|
26
|
+
router.post('/', (req, res) => {
|
|
27
|
+
try {
|
|
28
|
+
const state = req.body;
|
|
29
|
+
if (!state || typeof state !== 'object') {
|
|
30
|
+
res.status(400).json({ status: 'error', error: 'Invalid body' });
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const agentId = state.id || `agent-${Date.now()}`;
|
|
34
|
+
state.version = state.version || '0.1.0';
|
|
35
|
+
saveAgent(agentId, state);
|
|
36
|
+
res.json({ status: 'ok', data: { id: agentId } });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Get latest agent state
|
|
20
43
|
router.get('/:id', (req, res) => {
|
|
21
44
|
try {
|
|
22
45
|
const agent = loadAgent(req.params.id);
|
|
@@ -30,6 +53,7 @@ router.get('/:id', (req, res) => {
|
|
|
30
53
|
res.status(500).json({ status: 'error', error: err.message });
|
|
31
54
|
}
|
|
32
55
|
});
|
|
56
|
+
// Save/update agent (creates version)
|
|
33
57
|
router.put('/:id', (req, res) => {
|
|
34
58
|
try {
|
|
35
59
|
const state = req.body;
|
|
@@ -37,13 +61,45 @@ router.put('/:id', (req, res) => {
|
|
|
37
61
|
res.status(400).json({ status: 'error', error: 'Invalid body' });
|
|
38
62
|
return;
|
|
39
63
|
}
|
|
64
|
+
// Create version if agent exists and version changed
|
|
65
|
+
const existing = loadAgent(req.params.id);
|
|
66
|
+
if (existing && state.version && state.version !== existing.version) {
|
|
67
|
+
createAgentVersion(req.params.id, existing.version, 'Auto-saved version');
|
|
68
|
+
}
|
|
69
|
+
saveAgent(req.params.id, state);
|
|
70
|
+
res.json({ status: 'ok', data: { id: req.params.id, version: state.version } });
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
74
|
+
}
|
|
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;
|
|
40
88
|
saveAgent(req.params.id, state);
|
|
41
|
-
res.json({
|
|
89
|
+
res.json({
|
|
90
|
+
status: 'ok',
|
|
91
|
+
data: {
|
|
92
|
+
id: req.params.id,
|
|
93
|
+
version: state.version,
|
|
94
|
+
versionId: versionEntry?.id ?? null,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
42
97
|
}
|
|
43
98
|
catch (err) {
|
|
44
99
|
res.status(500).json({ status: 'error', error: err.message });
|
|
45
100
|
}
|
|
46
101
|
});
|
|
102
|
+
// Delete agent
|
|
47
103
|
router.delete('/:id', (req, res) => {
|
|
48
104
|
try {
|
|
49
105
|
const deleted = deleteAgent(req.params.id);
|
|
@@ -57,5 +113,49 @@ router.delete('/:id', (req, res) => {
|
|
|
57
113
|
res.status(500).json({ status: 'error', error: err.message });
|
|
58
114
|
}
|
|
59
115
|
});
|
|
116
|
+
// List agent versions
|
|
117
|
+
router.get('/:id/versions', (req, res) => {
|
|
118
|
+
try {
|
|
119
|
+
const versions = listAgentVersions(req.params.id);
|
|
120
|
+
const formatted = versions.map(v => ({
|
|
121
|
+
id: v.id,
|
|
122
|
+
version: v.version,
|
|
123
|
+
timestamp: v.timestamp,
|
|
124
|
+
label: v.label,
|
|
125
|
+
changeSummary: v.changeSummary,
|
|
126
|
+
}));
|
|
127
|
+
res.json({ status: 'ok', data: formatted });
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Restore agent version
|
|
134
|
+
router.post('/:id/versions/:version/restore', (req, res) => {
|
|
135
|
+
try {
|
|
136
|
+
const restored = restoreAgentVersion(req.params.id, req.params.version);
|
|
137
|
+
if (!restored) {
|
|
138
|
+
res.status(404).json({ status: 'error', error: 'Version not found' });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
res.json({ status: 'ok', data: { restored: req.params.version } });
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
// Delete agent version
|
|
148
|
+
router.delete('/:id/versions/:version', (req, res) => {
|
|
149
|
+
try {
|
|
150
|
+
const deleted = deleteAgentVersion(req.params.id, req.params.version);
|
|
151
|
+
if (!deleted) {
|
|
152
|
+
res.status(404).json({ status: 'error', error: 'Version not found' });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
res.json({ status: 'ok' });
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
res.status(500).json({ status: 'error', error: err.message });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
60
161
|
export default router;
|
|
61
|
-
//# sourceMappingURL=agents.js.map
|
|
@@ -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;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["../../../server/routes/connectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,QAAA,MAAM,MAAM,4CAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"connectors.d.ts","sourceRoot":"","sources":["../../../server/routes/connectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA4lBxB,eAAe,MAAM,CAAC"}
|
|
@@ -324,5 +324,228 @@ router.delete('/auth/:service', (req, res) => {
|
|
|
324
324
|
saveAuth(auth);
|
|
325
325
|
res.json({ status: 'ok' });
|
|
326
326
|
});
|
|
327
|
+
// ── In-memory session keys (never persisted to disk) ──
|
|
328
|
+
const sessionKeys = new Map();
|
|
329
|
+
// ── Notion helpers ──
|
|
330
|
+
function extractPageId(url) {
|
|
331
|
+
const hex32 = url.match(/([a-f0-9]{32})(?:[?#/]|$)/i);
|
|
332
|
+
if (hex32)
|
|
333
|
+
return hex32[1];
|
|
334
|
+
const uuid = url.match(/([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})/i);
|
|
335
|
+
return uuid ? uuid[1].replace(/-/g, '') : null;
|
|
336
|
+
}
|
|
337
|
+
function notionHeaders(key) {
|
|
338
|
+
return { 'Authorization': `Bearer ${key}`, 'Notion-Version': '2022-06-28', 'Content-Type': 'application/json' };
|
|
339
|
+
}
|
|
340
|
+
function getBlockText(block) {
|
|
341
|
+
const content = block[block.type];
|
|
342
|
+
if (typeof content !== 'object' || !content || Array.isArray(content))
|
|
343
|
+
return '';
|
|
344
|
+
const rt = content.rich_text;
|
|
345
|
+
if (!Array.isArray(rt))
|
|
346
|
+
return '';
|
|
347
|
+
return rt.map(r => (typeof r === 'object' && r !== null ? String(r.plain_text ?? '') : '')).join('');
|
|
348
|
+
}
|
|
349
|
+
function blocksToMarkdown(blocks) {
|
|
350
|
+
const lines = [];
|
|
351
|
+
for (const b of blocks) {
|
|
352
|
+
const text = getBlockText(b);
|
|
353
|
+
if (!text)
|
|
354
|
+
continue;
|
|
355
|
+
if (b.type === 'heading_1')
|
|
356
|
+
lines.push(`# ${text}`);
|
|
357
|
+
else if (b.type === 'heading_2')
|
|
358
|
+
lines.push(`## ${text}`);
|
|
359
|
+
else if (b.type === 'heading_3')
|
|
360
|
+
lines.push(`### ${text}`);
|
|
361
|
+
else if (b.type === 'bulleted_list_item')
|
|
362
|
+
lines.push(`- ${text}`);
|
|
363
|
+
else if (b.type === 'numbered_list_item')
|
|
364
|
+
lines.push(`1. ${text}`);
|
|
365
|
+
else if (b.type === 'code')
|
|
366
|
+
lines.push(`\`\`\`\n${text}\n\`\`\``);
|
|
367
|
+
else
|
|
368
|
+
lines.push(text);
|
|
369
|
+
}
|
|
370
|
+
return lines.join('\n');
|
|
371
|
+
}
|
|
372
|
+
function getPageTitle(page) {
|
|
373
|
+
const titleProp = Object.values(page.properties).find(p => p.type === 'title');
|
|
374
|
+
return titleProp?.title?.[0]?.plain_text ?? page.id;
|
|
375
|
+
}
|
|
376
|
+
async function fetchAllBlocks(pageId, key) {
|
|
377
|
+
const blocks = [];
|
|
378
|
+
let cursor;
|
|
379
|
+
do {
|
|
380
|
+
const qs = cursor ? `?start_cursor=${encodeURIComponent(cursor)}` : '';
|
|
381
|
+
const resp = await fetch(`https://api.notion.com/v1/blocks/${pageId}/children${qs}`, { headers: notionHeaders(key) });
|
|
382
|
+
if (!resp.ok)
|
|
383
|
+
break;
|
|
384
|
+
const data = await resp.json();
|
|
385
|
+
blocks.push(...data.results);
|
|
386
|
+
cursor = data.has_more && data.next_cursor ? data.next_cursor : undefined;
|
|
387
|
+
} while (cursor);
|
|
388
|
+
return blocks;
|
|
389
|
+
}
|
|
390
|
+
async function fetchPage(pageId, key) {
|
|
391
|
+
const resp = await fetch(`https://api.notion.com/v1/pages/${pageId}`, { headers: notionHeaders(key) });
|
|
392
|
+
if (!resp.ok)
|
|
393
|
+
return null;
|
|
394
|
+
const page = await resp.json();
|
|
395
|
+
const title = getPageTitle(page);
|
|
396
|
+
const blocks = await fetchAllBlocks(pageId, key);
|
|
397
|
+
const content = blocksToMarkdown(blocks);
|
|
398
|
+
return { id: pageId, title, content, tokens: Math.ceil(content.length / 4) };
|
|
399
|
+
}
|
|
400
|
+
async function queryDatabase(dbId, key) {
|
|
401
|
+
const items = [];
|
|
402
|
+
let cursor;
|
|
403
|
+
do {
|
|
404
|
+
const body = JSON.stringify(cursor ? { start_cursor: cursor } : {});
|
|
405
|
+
const resp = await fetch(`https://api.notion.com/v1/databases/${dbId}/query`, { method: 'POST', headers: notionHeaders(key), body });
|
|
406
|
+
if (!resp.ok)
|
|
407
|
+
break;
|
|
408
|
+
const data = await resp.json();
|
|
409
|
+
for (const row of data.results) {
|
|
410
|
+
const title = getPageTitle(row);
|
|
411
|
+
items.push({ id: row.id, title, content: `# ${title}`, tokens: Math.ceil(title.length / 4) + 10 });
|
|
412
|
+
}
|
|
413
|
+
cursor = data.has_more && data.next_cursor ? data.next_cursor : undefined;
|
|
414
|
+
} while (cursor);
|
|
415
|
+
return items;
|
|
416
|
+
}
|
|
417
|
+
async function searchWorkspace(key) {
|
|
418
|
+
const body = JSON.stringify({ sort: { direction: 'descending', timestamp: 'last_edited_time' }, page_size: 10 });
|
|
419
|
+
const resp = await fetch('https://api.notion.com/v1/search', { method: 'POST', headers: notionHeaders(key), body });
|
|
420
|
+
if (!resp.ok)
|
|
421
|
+
return [];
|
|
422
|
+
const data = await resp.json();
|
|
423
|
+
const items = [];
|
|
424
|
+
for (const result of data.results) {
|
|
425
|
+
if (result.object !== 'page')
|
|
426
|
+
continue;
|
|
427
|
+
const page = await fetchPage(result.id, key);
|
|
428
|
+
if (page)
|
|
429
|
+
items.push(page);
|
|
430
|
+
}
|
|
431
|
+
return items;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* POST /api/connectors/notion/test
|
|
435
|
+
* Validate a Notion API key by calling users/me.
|
|
436
|
+
*/
|
|
437
|
+
router.post('/notion/test', async (req, res) => {
|
|
438
|
+
const body = req.body;
|
|
439
|
+
const apiKey = typeof body.apiKey === 'string' ? body.apiKey : '';
|
|
440
|
+
if (!apiKey) {
|
|
441
|
+
res.status(400).json({ status: 'error', error: 'Missing apiKey' });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const resp = await fetch('https://api.notion.com/v1/users/me', {
|
|
446
|
+
headers: { 'Authorization': `Bearer ${apiKey}`, 'Notion-Version': '2022-06-28' },
|
|
447
|
+
});
|
|
448
|
+
if (resp.status === 401) {
|
|
449
|
+
res.status(401).json({ status: 'error', error: 'Invalid Notion API key. Create one at notion.so/my-integrations' });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
if (resp.status === 429) {
|
|
453
|
+
const retryAfter = resp.headers.get('Retry-After') ?? '60';
|
|
454
|
+
res.status(429).json({ status: 'error', error: `Notion rate limit hit. Retry in ${retryAfter}s` });
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
if (!resp.ok) {
|
|
458
|
+
res.status(resp.status).json({ status: 'error', error: `Notion API error: ${resp.status}` });
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const user = await resp.json();
|
|
462
|
+
sessionKeys.set('notion', apiKey);
|
|
463
|
+
res.json({ status: 'ok', data: { user: user.name ?? user.id } });
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
res.status(500).json({ status: 'error', error: 'Connection error. Check your network.' });
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
/**
|
|
470
|
+
* POST /api/connectors/notion/fetch
|
|
471
|
+
* Fetch pages/databases from Notion and return as markdown items.
|
|
472
|
+
* Body: { apiKey?, databaseIds?: string[], pageUrls?: string[] }
|
|
473
|
+
*/
|
|
474
|
+
router.post('/notion/fetch', async (req, res) => {
|
|
475
|
+
const body = req.body;
|
|
476
|
+
const apiKey = typeof body.apiKey === 'string' && body.apiKey
|
|
477
|
+
? body.apiKey
|
|
478
|
+
: (sessionKeys.get('notion') ?? '');
|
|
479
|
+
if (!apiKey) {
|
|
480
|
+
res.status(401).json({ status: 'error', error: 'No API key. Test connection first.' });
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const databaseIds = Array.isArray(body.databaseIds)
|
|
484
|
+
? body.databaseIds.filter((s) => typeof s === 'string')
|
|
485
|
+
: [];
|
|
486
|
+
const pageUrls = Array.isArray(body.pageUrls)
|
|
487
|
+
? body.pageUrls.filter((s) => typeof s === 'string')
|
|
488
|
+
: [];
|
|
489
|
+
try {
|
|
490
|
+
const items = [];
|
|
491
|
+
for (const dbId of databaseIds) {
|
|
492
|
+
items.push(...(await queryDatabase(dbId.trim(), apiKey)));
|
|
493
|
+
}
|
|
494
|
+
for (const url of pageUrls) {
|
|
495
|
+
const pageId = extractPageId(url);
|
|
496
|
+
if (!pageId)
|
|
497
|
+
continue;
|
|
498
|
+
const page = await fetchPage(pageId, apiKey);
|
|
499
|
+
if (page)
|
|
500
|
+
items.push(page);
|
|
501
|
+
}
|
|
502
|
+
if (databaseIds.length === 0 && pageUrls.length === 0) {
|
|
503
|
+
items.push(...(await searchWorkspace(apiKey)));
|
|
504
|
+
}
|
|
505
|
+
res.json({ status: 'ok', data: items });
|
|
506
|
+
}
|
|
507
|
+
catch (err) {
|
|
508
|
+
const msg = err instanceof Error ? err.message : '';
|
|
509
|
+
if (msg.includes('429')) {
|
|
510
|
+
res.status(429).json({ status: 'error', error: 'Notion rate limit hit. Retry in 60s' });
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
res.status(500).json({ status: 'error', error: 'Failed to fetch from Notion. Check API key permissions.' });
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
/**
|
|
517
|
+
* POST /api/connectors/notion/search
|
|
518
|
+
* Search the Notion workspace by keyword.
|
|
519
|
+
* Body: { apiKey?, query: string }
|
|
520
|
+
*/
|
|
521
|
+
router.post('/notion/search', async (req, res) => {
|
|
522
|
+
const body = req.body;
|
|
523
|
+
const apiKey = (typeof body.apiKey === 'string' && body.apiKey) || (sessionKeys.get('notion') ?? '');
|
|
524
|
+
if (!apiKey) {
|
|
525
|
+
res.status(401).json({ status: 'error', error: 'No API key. Test connection first.' });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const query = typeof body.query === 'string' ? body.query : '';
|
|
529
|
+
try {
|
|
530
|
+
const resp = await fetch('https://api.notion.com/v1/search', {
|
|
531
|
+
method: 'POST', headers: notionHeaders(apiKey), body: JSON.stringify(query ? { query } : {}),
|
|
532
|
+
});
|
|
533
|
+
if (!resp.ok) {
|
|
534
|
+
res.status(resp.status).json({ status: 'error', error: `Notion search failed: ${resp.status}` });
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
const data = await resp.json();
|
|
538
|
+
const results = data.results.map(r => ({
|
|
539
|
+
id: r.id,
|
|
540
|
+
title: r.object === 'database'
|
|
541
|
+
? (r.title?.[0]?.plain_text ?? r.id)
|
|
542
|
+
: getPageTitle(r),
|
|
543
|
+
type: r.object,
|
|
544
|
+
}));
|
|
545
|
+
res.json({ status: 'ok', data: results });
|
|
546
|
+
}
|
|
547
|
+
catch {
|
|
548
|
+
res.status(500).json({ status: 'error', error: 'Failed to search Notion workspace.' });
|
|
549
|
+
}
|
|
550
|
+
});
|
|
327
551
|
export default router;
|
|
328
|
-
//# sourceMappingURL=connectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lessons.d.ts","sourceRoot":"","sources":["../../../server/routes/lessons.ts"],"names":[],"mappings":"AAOA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8DxB,eAAe,MAAM,CAAC"}
|