modular-studio 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +122 -41
  2. package/dist/assets/Badge-DrUmDAXz.js +1 -0
  3. package/dist/assets/Input-ndEGQSgx.js +1 -0
  4. package/dist/assets/KnowledgeTab-CxlC76Rf.js +4 -0
  5. package/dist/assets/MemoryTab-CUScYWs9.js +16 -0
  6. package/dist/assets/QualificationTab-BqnWSQHm.js +1 -0
  7. package/dist/assets/ReviewTab-DKYl6cR9.js +103 -0
  8. package/dist/assets/Section-CgmwAj_2.js +1 -0
  9. package/dist/assets/TestTab-iJ2vCf9l.js +33 -0
  10. package/dist/assets/ToolsTab-C10Ulm8b.js +1 -0
  11. package/dist/assets/conversationStore-CkfEU2eV.js +1 -0
  12. package/dist/assets/icons-MKpPNvV8.js +1 -0
  13. package/dist/assets/index-B_ip7Amg.css +1 -0
  14. package/dist/assets/index-gBy3427k.js +143 -0
  15. package/dist/assets/{jszip.min-BK6ZQWkj.js → jszip.min-wf-D3Ix_.js} +1 -1
  16. package/dist/assets/markdown-DWF7F0i0.js +29 -0
  17. package/dist/assets/services-CTWXQK6j.js +356 -0
  18. package/dist/assets/stores-CeKWz7ou.js +1 -0
  19. package/dist/assets/vendor-D1h_O76p.js +9 -0
  20. package/dist/index.html +20 -16
  21. package/dist-server/bin/modular-mcp.js +0 -1
  22. package/dist-server/bin/modular-studio.js +0 -1
  23. package/dist-server/server/config.js +0 -1
  24. package/dist-server/server/data/mcp-tokens.json +3 -3
  25. package/dist-server/server/index.d.ts.map +1 -1
  26. package/dist-server/server/index.js +6 -1
  27. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  28. package/dist-server/server/mcp/manager.js +16 -3
  29. package/dist-server/server/mcp/modular-server.js +0 -1
  30. package/dist-server/server/mcp/transport.js +0 -1
  31. package/dist-server/server/routes/agent-sdk.js +0 -1
  32. package/dist-server/server/routes/agents.d.ts +9 -5
  33. package/dist-server/server/routes/agents.d.ts.map +1 -1
  34. package/dist-server/server/routes/agents.js +108 -8
  35. package/dist-server/server/routes/auth-codex.js +0 -1
  36. package/dist-server/server/routes/cache.d.ts +3 -0
  37. package/dist-server/server/routes/cache.d.ts.map +1 -0
  38. package/dist-server/server/routes/cache.js +55 -0
  39. package/dist-server/server/routes/capabilities.js +0 -1
  40. package/dist-server/server/routes/claude-config.js +0 -1
  41. package/dist-server/server/routes/connectors.d.ts.map +1 -1
  42. package/dist-server/server/routes/connectors.js +224 -1
  43. package/dist-server/server/routes/conversations.js +0 -1
  44. package/dist-server/server/routes/embeddings.js +0 -1
  45. package/dist-server/server/routes/health.js +0 -1
  46. package/dist-server/server/routes/knowledge.js +0 -1
  47. package/dist-server/server/routes/lessons.d.ts +3 -0
  48. package/dist-server/server/routes/lessons.d.ts.map +1 -0
  49. package/dist-server/server/routes/lessons.js +46 -0
  50. package/dist-server/server/routes/llm.js +0 -1
  51. package/dist-server/server/routes/mcp-oauth.js +0 -1
  52. package/dist-server/server/routes/mcp.js +0 -1
  53. package/dist-server/server/routes/memory.d.ts +3 -0
  54. package/dist-server/server/routes/memory.d.ts.map +1 -0
  55. package/dist-server/server/routes/memory.js +314 -0
  56. package/dist-server/server/routes/pipeline.js +0 -1
  57. package/dist-server/server/routes/providers.js +0 -1
  58. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  59. package/dist-server/server/routes/qualification.js +341 -75
  60. package/dist-server/server/routes/repo-index.d.ts.map +1 -1
  61. package/dist-server/server/routes/repo-index.js +7 -1
  62. package/dist-server/server/routes/runtime.js +0 -1
  63. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  64. package/dist-server/server/routes/skills-search.js +198 -8
  65. package/dist-server/server/routes/worktrees.js +0 -1
  66. package/dist-server/server/services/__tests__/embeddingService.test.js +0 -1
  67. package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
  68. package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
  69. package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
  70. package/dist-server/server/services/adapters/postgresAdapter.d.ts +29 -0
  71. package/dist-server/server/services/adapters/postgresAdapter.d.ts.map +1 -0
  72. package/dist-server/server/services/adapters/postgresAdapter.js +224 -0
  73. package/dist-server/server/services/adapters/sqliteAdapter.d.ts +28 -0
  74. package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -0
  75. package/dist-server/server/services/adapters/sqliteAdapter.js +219 -0
  76. package/dist-server/server/services/adapters/storageAdapter.d.ts +22 -0
  77. package/dist-server/server/services/adapters/storageAdapter.d.ts.map +1 -0
  78. package/dist-server/server/services/adapters/storageAdapter.js +1 -0
  79. package/dist-server/server/services/agentRunner.js +0 -1
  80. package/dist-server/server/services/agentStore.d.ts +19 -3
  81. package/dist-server/server/services/agentStore.d.ts.map +1 -1
  82. package/dist-server/server/services/agentStore.js +117 -23
  83. package/dist-server/server/services/contentStore.js +0 -1
  84. package/dist-server/server/services/correctionDetector.d.ts +22 -0
  85. package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
  86. package/dist-server/server/services/correctionDetector.js +91 -0
  87. package/dist-server/server/services/embeddingService.d.ts +2 -0
  88. package/dist-server/server/services/embeddingService.d.ts.map +1 -1
  89. package/dist-server/server/services/embeddingService.js +30 -19
  90. package/dist-server/server/services/factExtractor.js +0 -1
  91. package/dist-server/server/services/githubIndexer.js +0 -1
  92. package/dist-server/server/services/hindsightClient.d.ts +15 -0
  93. package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
  94. package/dist-server/server/services/hindsightClient.js +47 -0
  95. package/dist-server/server/services/lessonExtractor.d.ts +19 -0
  96. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
  97. package/dist-server/server/services/lessonExtractor.js +87 -0
  98. package/dist-server/server/services/mcpOAuth.js +0 -1
  99. package/dist-server/server/services/memoryScorer.js +0 -1
  100. package/dist-server/server/services/repoIndexer.js +0 -1
  101. package/dist-server/server/services/responseCache.d.ts +24 -0
  102. package/dist-server/server/services/responseCache.d.ts.map +1 -0
  103. package/dist-server/server/services/responseCache.js +163 -0
  104. package/dist-server/server/services/sqliteStore.d.ts +8 -0
  105. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  106. package/dist-server/server/services/sqliteStore.js +53 -14
  107. package/dist-server/server/services/teamRunner.js +0 -1
  108. package/dist-server/server/services/worktreeManager.js +0 -1
  109. package/dist-server/server/types.d.ts +5 -0
  110. package/dist-server/server/types.d.ts.map +1 -1
  111. package/dist-server/server/types.js +0 -1
  112. package/dist-server/server/utils/pathSecurity.js +0 -1
  113. package/dist-server/src/services/budgetAllocator.js +0 -1
  114. package/dist-server/src/services/contradictionDetector.js +0 -1
  115. package/dist-server/src/services/treeIndexer.js +0 -1
  116. package/dist-server/src/store/knowledgeBase.d.ts +11 -0
  117. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  118. package/dist-server/src/store/knowledgeBase.js +13 -1
  119. package/dist-server/src/store/lessonStore.d.ts +26 -0
  120. package/dist-server/src/store/lessonStore.d.ts.map +1 -0
  121. package/dist-server/src/store/lessonStore.js +64 -0
  122. package/dist-server/src/store/memoryStore.d.ts +118 -0
  123. package/dist-server/src/store/memoryStore.d.ts.map +1 -0
  124. package/dist-server/src/store/memoryStore.js +272 -0
  125. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  126. package/package.json +9 -1
  127. package/dist/assets/graphPopulator-B3rQxb5A.js +0 -1
  128. package/dist/assets/index-BA_J-aHx.js +0 -686
  129. package/dist/assets/index-C7vpqKVZ.css +0 -1
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "https://mcp.notion.com/mcp": {
3
- "access_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:e693KzFNDfCs6D6d:4rsQgaVukHpKZxUbeO7UESurbGVbXYHe",
4
- "refresh_token": "dce1a5b0-d0c3-4ca4-a786-35efb72a5a4f:e693KzFNDfCs6D6d:3AoiFsO9x0OJZyxsBFKLPZ7cXg6sIRyK",
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": 1773271627737
7
+ "expires_at": 1773338149330
8
8
  }
9
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.ts"],"names":[],"mappings":"AA+BA,wBAAgB,SAAS,gDA4FxB;AA+BD,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,sGAwB9C"}
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;IA0B1B,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"}
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()?.split('.')[0] || '';
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
@@ -489,4 +489,3 @@ function consolidateMemory(facts) {
489
489
  promoted,
490
490
  };
491
491
  }
492
- //# sourceMappingURL=modular-server.js.map
@@ -58,4 +58,3 @@ export function setupGracefulShutdown(server) {
58
58
  shutdown('unhandledRejection');
59
59
  });
60
60
  }
61
- //# sourceMappingURL=transport.js.map
@@ -96,4 +96,3 @@ router.get("/status", async (_req, res) => {
96
96
  }
97
97
  });
98
98
  export default router;
99
- //# sourceMappingURL=agent-sdk.js.map
@@ -1,9 +1,13 @@
1
1
  /**
2
- * Agent CRUD Routes
3
- * GET /api/agents — list all (summary only)
4
- * GET /api/agents/:id full state
5
- * PUT /api/agents/:id save/update
6
- * DELETE /api/agents/:id delete
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;;;;;;GAMG;AAMH,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmDxB,eAAe,MAAM,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"}
@@ -1,13 +1,18 @@
1
1
  /**
2
- * Agent CRUD Routes
3
- * GET /api/agents — list all (summary only)
4
- * GET /api/agents/:id full state
5
- * PUT /api/agents/:id save/update
6
- * DELETE /api/agents/:id delete
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({ status: 'ok', data: { id: req.params.id } });
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
@@ -48,4 +48,3 @@ router.post('/complete/:sessionId', (req, res) => {
48
48
  res.json(resp);
49
49
  });
50
50
  export default router;
51
- //# sourceMappingURL=auth-codex.js.map
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=cache.d.ts.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;
@@ -29,4 +29,3 @@ router.get('/:providerId', (req, res) => {
29
29
  res.json({ status: 'ok', data: resolveMatrix(req.params.providerId) });
30
30
  });
31
31
  export default router;
32
- //# sourceMappingURL=capabilities.js.map
@@ -143,4 +143,3 @@ router.get('/project-mcp', (req, res) => {
143
143
  }
144
144
  });
145
145
  export default router;
146
- //# sourceMappingURL=claude-config.js.map
@@ -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;AA0XxB,eAAe,MAAM,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
@@ -108,4 +108,3 @@ router.delete('/:id', async (req, res) => {
108
108
  }
109
109
  });
110
110
  export default router;
111
- //# sourceMappingURL=conversations.js.map
@@ -152,4 +152,3 @@ router.post('/search', async (req, res) => {
152
152
  }
153
153
  });
154
154
  export default router;
155
- //# sourceMappingURL=embeddings.js.map
@@ -291,4 +291,3 @@ router.post('/skills/:id/update', async (req, res) => {
291
291
  }
292
292
  });
293
293
  export default router;
294
- //# sourceMappingURL=health.js.map
@@ -531,4 +531,3 @@ router.post('/embed', async (req, res) => {
531
531
  }
532
532
  });
533
533
  export default router;
534
- //# sourceMappingURL=knowledge.js.map
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=lessons.d.ts.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"}