@intranefr/superbackend 1.5.1 → 1.5.3

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 (73) hide show
  1. package/.env.example +10 -0
  2. package/index.js +2 -0
  3. package/manage.js +745 -0
  4. package/package.json +5 -2
  5. package/src/controllers/admin.controller.js +79 -6
  6. package/src/controllers/adminAgents.controller.js +37 -0
  7. package/src/controllers/adminExperiments.controller.js +200 -0
  8. package/src/controllers/adminLlm.controller.js +19 -0
  9. package/src/controllers/adminMarkdowns.controller.js +157 -0
  10. package/src/controllers/adminScripts.controller.js +243 -74
  11. package/src/controllers/adminTelegram.controller.js +72 -0
  12. package/src/controllers/experiments.controller.js +85 -0
  13. package/src/controllers/internalExperiments.controller.js +17 -0
  14. package/src/controllers/markdowns.controller.js +42 -0
  15. package/src/helpers/mongooseHelper.js +258 -0
  16. package/src/helpers/scriptBase.js +230 -0
  17. package/src/helpers/scriptRunner.js +335 -0
  18. package/src/middleware.js +195 -34
  19. package/src/models/Agent.js +105 -0
  20. package/src/models/AgentMessage.js +82 -0
  21. package/src/models/CacheEntry.js +1 -1
  22. package/src/models/ConsoleLog.js +1 -1
  23. package/src/models/Experiment.js +75 -0
  24. package/src/models/ExperimentAssignment.js +23 -0
  25. package/src/models/ExperimentEvent.js +26 -0
  26. package/src/models/ExperimentMetricBucket.js +30 -0
  27. package/src/models/GlobalSetting.js +1 -2
  28. package/src/models/Markdown.js +75 -0
  29. package/src/models/RateLimitCounter.js +1 -1
  30. package/src/models/ScriptDefinition.js +1 -0
  31. package/src/models/ScriptRun.js +8 -0
  32. package/src/models/TelegramBot.js +42 -0
  33. package/src/models/Webhook.js +2 -0
  34. package/src/routes/admin.routes.js +2 -0
  35. package/src/routes/adminAgents.routes.js +13 -0
  36. package/src/routes/adminConsoleManager.routes.js +1 -1
  37. package/src/routes/adminExperiments.routes.js +29 -0
  38. package/src/routes/adminLlm.routes.js +1 -0
  39. package/src/routes/adminMarkdowns.routes.js +16 -0
  40. package/src/routes/adminScripts.routes.js +4 -1
  41. package/src/routes/adminTelegram.routes.js +14 -0
  42. package/src/routes/blogInternal.routes.js +2 -2
  43. package/src/routes/experiments.routes.js +30 -0
  44. package/src/routes/internalExperiments.routes.js +15 -0
  45. package/src/routes/markdowns.routes.js +16 -0
  46. package/src/services/agent.service.js +546 -0
  47. package/src/services/agentHistory.service.js +345 -0
  48. package/src/services/agentTools.service.js +578 -0
  49. package/src/services/blogCronsBootstrap.service.js +7 -6
  50. package/src/services/consoleManager.service.js +56 -18
  51. package/src/services/consoleOverride.service.js +1 -0
  52. package/src/services/experiments.service.js +273 -0
  53. package/src/services/experimentsAggregation.service.js +308 -0
  54. package/src/services/experimentsCronsBootstrap.service.js +118 -0
  55. package/src/services/experimentsRetention.service.js +43 -0
  56. package/src/services/experimentsWs.service.js +134 -0
  57. package/src/services/globalSettings.service.js +15 -0
  58. package/src/services/jsonConfigs.service.js +24 -12
  59. package/src/services/llm.service.js +219 -6
  60. package/src/services/markdowns.service.js +522 -0
  61. package/src/services/scriptsRunner.service.js +514 -23
  62. package/src/services/telegram.service.js +130 -0
  63. package/src/utils/rbac/rightsRegistry.js +4 -0
  64. package/views/admin-agents.ejs +273 -0
  65. package/views/admin-coolify-deploy.ejs +8 -8
  66. package/views/admin-dashboard.ejs +63 -12
  67. package/views/admin-experiments.ejs +91 -0
  68. package/views/admin-markdowns.ejs +905 -0
  69. package/views/admin-scripts.ejs +817 -6
  70. package/views/admin-telegram.ejs +269 -0
  71. package/views/partials/dashboard/nav-items.ejs +4 -0
  72. package/views/partials/dashboard/palette.ejs +5 -3
  73. package/src/middleware/internalCronAuth.js +0 -29
package/src/middleware.js CHANGED
@@ -1,10 +1,20 @@
1
1
  const consoleOverride = require("./services/consoleOverride.service");
2
- const consoleManager = require("./services/consoleManager.service");
2
+ const { consoleManager } = require("./services/consoleManager.service");
3
3
 
4
4
  // Initialize console override service early to capture all logs
5
5
  // Avoid keeping timers/streams alive during Jest runs.
6
6
  if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
7
7
  consoleOverride.init()
8
+
9
+ // Initialize console manager after a short delay to ensure consoleOverride is fully set up
10
+ setTimeout(() => {
11
+ // Set module prefix for this middleware
12
+ consoleManager.setModulePrefix('middleware');
13
+
14
+ // Initialize console manager early to enable prefixing for all subsequent logs
15
+ consoleManager.init();
16
+ console.log("[Console Manager] Initialized - prefixing enabled");
17
+ }, 20);
8
18
  }
9
19
 
10
20
  const express = require("express");
@@ -119,6 +129,11 @@ function createMiddleware(options = {}) {
119
129
  attachTerminalWebsocketServer,
120
130
  } = require("./services/terminalsWs.service");
121
131
  attachTerminalWebsocketServer(server, { basePathPrefix: adminPath });
132
+
133
+ const {
134
+ attachExperimentsWebsocketServer,
135
+ } = require("./services/experimentsWs.service");
136
+ attachExperimentsWebsocketServer(server);
122
137
  };
123
138
 
124
139
  if (!errorCaptureInitialized) {
@@ -146,6 +161,8 @@ function createMiddleware(options = {}) {
146
161
  maxPoolSize: 10,
147
162
  };
148
163
 
164
+ const telegramService = require("./services/telegram.service");
165
+
149
166
  // Return a promise that resolves when connection is established
150
167
  const connectionPromise = mongoose
151
168
  .connect(mongoUri, connectionOptions)
@@ -156,17 +173,13 @@ function createMiddleware(options = {}) {
156
173
  await healthChecksScheduler.start();
157
174
  await healthChecksBootstrap.bootstrap();
158
175
  await blogCronsBootstrap.bootstrap();
176
+ await require("./services/experimentsCronsBootstrap.service").bootstrap();
159
177
 
160
- // Initialize console manager AFTER database is connected
161
- if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
162
- const consoleManagerEnabled = await isConsoleManagerEnabled();
163
- if (consoleManagerEnabled) {
164
- consoleManager.init();
165
- console.log("[Console Manager] Initialized");
166
- } else {
167
- console.log("[Console Manager] Disabled - console methods not overridden");
168
- }
169
- }
178
+ // Initialize Telegram bots
179
+ await telegramService.init();
180
+
181
+ // Console manager is already initialized early in the middleware
182
+ console.log("[Console Manager] MongoDB connection established");
170
183
 
171
184
  return true;
172
185
  })
@@ -254,12 +267,20 @@ function createMiddleware(options = {}) {
254
267
  blogCronsBootstrap.bootstrap().catch((err) => {
255
268
  console.error("Failed to bootstrap blog crons:", err);
256
269
  });
270
+
271
+ require("./services/experimentsCronsBootstrap.service")
272
+ .bootstrap()
273
+ .catch((err) => {
274
+ console.error("Failed to bootstrap experiments crons:", err);
275
+ });
257
276
 
258
277
  // Initialize console manager AFTER database is already connected
259
278
  if (process.env.NODE_ENV !== "test" && !process.env.JEST_WORKER_ID) {
260
279
  isConsoleManagerEnabled().then(consoleManagerEnabled => {
261
280
  if (consoleManagerEnabled) {
262
281
  consoleManager.init();
282
+ // Set module prefix after initialization
283
+ consoleManager.setModulePrefix('middleware');
263
284
  console.log("[Console Manager] Initialized");
264
285
  } else {
265
286
  console.log("[Console Manager] Disabled - console methods not overridden");
@@ -268,6 +289,8 @@ function createMiddleware(options = {}) {
268
289
  console.error("[Console Manager] Error checking enabled status:", error);
269
290
  console.log("[Console Manager] Fallback to enabled due to error");
270
291
  consoleManager.init();
292
+ // Set module prefix after initialization
293
+ consoleManager.setModulePrefix('middleware');
271
294
  console.log("[Console Manager] Initialized (fallback)");
272
295
  });
273
296
  }
@@ -467,8 +490,13 @@ function createMiddleware(options = {}) {
467
490
  });
468
491
  });
469
492
 
470
- router.get(`${adminPath}/rbac`, basicAuth, (req, res) => {
471
- const templatePath = path.join(__dirname, "..", "views", "admin-rbac.ejs");
493
+ router.get(`${adminPath}/experiments`, basicAuth, (req, res) => {
494
+ const templatePath = path.join(
495
+ __dirname,
496
+ "..",
497
+ "views",
498
+ "admin-experiments.ejs",
499
+ );
472
500
  fs.readFile(templatePath, "utf8", (err, template) => {
473
501
  if (err) {
474
502
  console.error("Error reading template:", err);
@@ -493,13 +521,8 @@ function createMiddleware(options = {}) {
493
521
  });
494
522
  });
495
523
 
496
- router.get(`${adminPath}/terminals`, basicAuth, (req, res) => {
497
- const templatePath = path.join(
498
- __dirname,
499
- "..",
500
- "views",
501
- "admin-terminals.ejs",
502
- );
524
+ router.get(`${adminPath}/rbac`, basicAuth, (req, res) => {
525
+ const templatePath = path.join(__dirname, "..", "views", "admin-rbac.ejs");
503
526
  fs.readFile(templatePath, "utf8", (err, template) => {
504
527
  if (err) {
505
528
  console.error("Error reading template:", err);
@@ -511,7 +534,6 @@ function createMiddleware(options = {}) {
511
534
  {
512
535
  baseUrl: req.baseUrl,
513
536
  adminPath,
514
- endpointRegistry,
515
537
  },
516
538
  {
517
539
  filename: templatePath,
@@ -525,12 +547,12 @@ function createMiddleware(options = {}) {
525
547
  });
526
548
  });
527
549
 
528
- router.get(`${adminPath}/scripts`, basicAuth, (req, res) => {
550
+ router.get(`${adminPath}/terminals`, basicAuth, (req, res) => {
529
551
  const templatePath = path.join(
530
552
  __dirname,
531
553
  "..",
532
554
  "views",
533
- "admin-scripts.ejs",
555
+ "admin-terminals.ejs",
534
556
  );
535
557
  fs.readFile(templatePath, "utf8", (err, template) => {
536
558
  if (err) {
@@ -557,8 +579,13 @@ function createMiddleware(options = {}) {
557
579
  });
558
580
  });
559
581
 
560
- router.get(`${adminPath}/crons`, basicAuth, (req, res) => {
561
- const templatePath = path.join(__dirname, "..", "views", "admin-crons.ejs");
582
+ router.get(`${adminPath}/scripts`, basicAuth, (req, res) => {
583
+ const templatePath = path.join(
584
+ __dirname,
585
+ "..",
586
+ "views",
587
+ "admin-scripts.ejs",
588
+ );
562
589
  fs.readFile(templatePath, "utf8", (err, template) => {
563
590
  if (err) {
564
591
  console.error("Error reading template:", err);
@@ -570,6 +597,7 @@ function createMiddleware(options = {}) {
570
597
  {
571
598
  baseUrl: req.baseUrl,
572
599
  adminPath,
600
+ endpointRegistry,
573
601
  },
574
602
  {
575
603
  filename: templatePath,
@@ -583,8 +611,8 @@ function createMiddleware(options = {}) {
583
611
  });
584
612
  });
585
613
 
586
- router.get(`${adminPath}/cache`, basicAuth, (req, res) => {
587
- const templatePath = path.join(__dirname, "..", "views", "admin-cache.ejs");
614
+ router.get(`${adminPath}/crons`, basicAuth, (req, res) => {
615
+ const templatePath = path.join(__dirname, "..", "views", "admin-crons.ejs");
588
616
  fs.readFile(templatePath, "utf8", (err, template) => {
589
617
  if (err) {
590
618
  console.error("Error reading template:", err);
@@ -609,13 +637,8 @@ function createMiddleware(options = {}) {
609
637
  });
610
638
  });
611
639
 
612
- router.get(`${adminPath}/db-browser`, basicAuth, (req, res) => {
613
- const templatePath = path.join(
614
- __dirname,
615
- "..",
616
- "views",
617
- "admin-db-browser.ejs",
618
- );
640
+ router.get(`${adminPath}/cache`, basicAuth, (req, res) => {
641
+ const templatePath = path.join(__dirname, "..", "views", "admin-cache.ejs");
619
642
  fs.readFile(templatePath, "utf8", (err, template) => {
620
643
  if (err) {
621
644
  console.error("Error reading template:", err);
@@ -640,6 +663,99 @@ function createMiddleware(options = {}) {
640
663
  });
641
664
  });
642
665
 
666
+ router.get(`${adminPath}/db-browser`, basicAuth, (req, res) => {
667
+ const templatePath = path.join(
668
+ __dirname,
669
+ "..",
670
+ "views",
671
+ "admin-db-browser.ejs",
672
+ );
673
+ fs.readFile(templatePath, "utf8", (err, template) => {
674
+ if (err) {
675
+ console.error("Error reading template:", err);
676
+ return res.status(500).send("Error loading page");
677
+ }
678
+ try {
679
+ const html = ejs.render(
680
+ template,
681
+ {
682
+ baseUrl: req.baseUrl,
683
+ adminPath,
684
+ },
685
+ {
686
+ filename: templatePath,
687
+ },
688
+ );
689
+ res.send(html);
690
+ } catch (renderErr) {
691
+ console.error("Error rendering template:", renderErr);
692
+ res.status(500).send("Error rendering page");
693
+ }
694
+ });
695
+ });
696
+
697
+ router.get(`${adminPath}/telegram`, basicAuth, (req, res) => {
698
+ const templatePath = path.join(
699
+ __dirname,
700
+ "..",
701
+ "views",
702
+ "admin-telegram.ejs",
703
+ );
704
+ fs.readFile(templatePath, "utf8", (err, template) => {
705
+ if (err) {
706
+ console.error("Error reading template:", err);
707
+ return res.status(500).send("Error loading page");
708
+ }
709
+ try {
710
+ const html = ejs.render(
711
+ template,
712
+ {
713
+ baseUrl: req.baseUrl,
714
+ adminPath,
715
+ },
716
+ {
717
+ filename: templatePath,
718
+ },
719
+ );
720
+ res.send(html);
721
+ } catch (renderErr) {
722
+ console.error("Error rendering template:", renderErr);
723
+ res.status(500).send("Error rendering page");
724
+ }
725
+ });
726
+ });
727
+
728
+ router.get(`${adminPath}/agents`, basicAuth, (req, res) => {
729
+ const templatePath = path.join(
730
+ __dirname,
731
+ "..",
732
+ "views",
733
+ "admin-agents.ejs",
734
+ );
735
+ fs.readFile(templatePath, "utf8", (err, template) => {
736
+ if (err) {
737
+ console.error("Error reading template:", err);
738
+ return res.status(500).send("Error loading page");
739
+ }
740
+ try {
741
+ const html = ejs.render(
742
+ template,
743
+ {
744
+ baseUrl: req.baseUrl,
745
+ adminPath,
746
+ },
747
+ {
748
+ filename: templatePath,
749
+ },
750
+ );
751
+ res.send(html);
752
+ } catch (renderErr) {
753
+ console.error("Error rendering template:", renderErr);
754
+ res.status(500).send("Error rendering page");
755
+ }
756
+ });
757
+ });
758
+
643
759
  router.use("/api/admin", require("./routes/admin.routes"));
644
760
  router.use("/api/admin/settings", require("./routes/globalSettings.routes"));
645
761
  router.use(
@@ -650,6 +766,10 @@ function createMiddleware(options = {}) {
650
766
  "/api/admin/json-configs",
651
767
  require("./routes/adminJsonConfigs.routes"),
652
768
  );
769
+ router.use(
770
+ "/api/admin/markdowns",
771
+ require("./routes/adminMarkdowns.routes"),
772
+ );
653
773
  router.use(
654
774
  "/api/admin/rate-limits",
655
775
  require("./routes/adminRateLimits.routes"),
@@ -677,6 +797,7 @@ function createMiddleware(options = {}) {
677
797
  require("./routes/adminDbBrowser.routes"),
678
798
  );
679
799
  router.use("/api/admin/terminals", require("./routes/adminTerminals.routes"));
800
+ router.use("/api/admin/experiments", require("./routes/adminExperiments.routes"));
680
801
  router.use("/api/admin/assets", require("./routes/adminAssets.routes"));
681
802
  router.use(
682
803
  "/api/admin/upload-namespaces",
@@ -698,6 +819,8 @@ function createMiddleware(options = {}) {
698
819
  require("./routes/adminAudit.routes"),
699
820
  );
700
821
  router.use("/api/admin/llm", require("./routes/adminLlm.routes"));
822
+ router.use("/api/admin/telegram", require("./routes/adminTelegram.routes"));
823
+ router.use("/api/admin/agents", require("./routes/adminAgents.routes"));
701
824
  router.use(
702
825
  "/api/admin/ejs-virtual",
703
826
  require("./routes/adminEjsVirtual.routes"),
@@ -712,6 +835,7 @@ function createMiddleware(options = {}) {
712
835
  router.use("/api/settings", require("./routes/globalSettings.routes"));
713
836
  router.use("/api/feature-flags", require("./routes/featureFlags.routes"));
714
837
  router.use("/api/json-configs", require("./routes/jsonConfigs.routes"));
838
+ router.use("/api/markdowns", require("./routes/markdowns.routes"));
715
839
  router.use("/api/assets", require("./routes/assets.routes"));
716
840
  router.use("/api/i18n", require("./routes/i18n.routes"));
717
841
  router.use("/api/headless", require("./routes/headless.routes"));
@@ -724,6 +848,7 @@ function createMiddleware(options = {}) {
724
848
  router.use("/api/ui-components", require("./routes/uiComponentsPublic.routes"));
725
849
  router.use("/api/rbac", require("./routes/rbac.routes"));
726
850
  router.use("/api/file-manager", require("./routes/fileManager.routes"));
851
+ router.use("/api/experiments", require("./routes/experiments.routes"));
727
852
 
728
853
  // Public blog APIs (headless)
729
854
  router.use("/api", require("./routes/blogPublic.routes"));
@@ -731,6 +856,9 @@ function createMiddleware(options = {}) {
731
856
  // Internal blog endpoints (used by HTTP CronJobs)
732
857
  router.use("/api/internal", require("./routes/blogInternal.routes"));
733
858
 
859
+ // Internal experiments endpoints (used by HTTP CronJobs)
860
+ router.use("/api/internal", require("./routes/internalExperiments.routes"));
861
+
734
862
  // Public health checks status (gated by global setting)
735
863
  router.use(
736
864
  "/api/health-checks",
@@ -1311,6 +1439,39 @@ function createMiddleware(options = {}) {
1311
1439
  });
1312
1440
  });
1313
1441
 
1442
+ // Admin markdowns page (protected by basic auth)
1443
+ router.get(`${adminPath}/markdowns`, basicAuth, (req, res) => {
1444
+ const templatePath = path.join(
1445
+ __dirname,
1446
+ "..",
1447
+ "views",
1448
+ "admin-markdowns.ejs",
1449
+ );
1450
+ fs.readFile(templatePath, "utf8", (err, template) => {
1451
+ if (err) {
1452
+ console.error("Error reading template:", err);
1453
+ return res.status(500).send("Error loading page");
1454
+ }
1455
+ try {
1456
+ const html = ejs.render(
1457
+ template,
1458
+ {
1459
+ baseUrl: req.baseUrl,
1460
+ adminPath,
1461
+ endpointRegistry,
1462
+ },
1463
+ {
1464
+ filename: templatePath,
1465
+ },
1466
+ );
1467
+ res.send(html);
1468
+ } catch (renderErr) {
1469
+ console.error("Error rendering template:", renderErr);
1470
+ res.status(500).send("Error rendering page");
1471
+ }
1472
+ });
1473
+ });
1474
+
1314
1475
  // Admin assets page (protected by basic auth)
1315
1476
  router.get(`${adminPath}/assets`, basicAuth, (req, res) => {
1316
1477
  const templatePath = path.join(
@@ -0,0 +1,105 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const agentSchema = new mongoose.Schema({
4
+ name: {
5
+ type: String,
6
+ required: true,
7
+ trim: true
8
+ },
9
+ systemPrompt: {
10
+ type: String,
11
+ default: `You are a helpful assistant with access to specific tools for querying data.
12
+
13
+ AVAILABLE TOOLS:
14
+
15
+ 1. query_database: Query the MongoDB database for insights.
16
+ - Parameters:
17
+ - modelName (required): The name of the Mongoose model (e.g., User, Markdown, AuditEvent)
18
+ - query (required): The MongoDB query object
19
+ - limit (optional): Limit the number of results (default: 5)
20
+ - Usage: Use this when you need to fetch specific data from the database.
21
+
22
+ 2. get_system_stats: Get general statistics about the system.
23
+ - Parameters: None
24
+ - Usage: Use this when you need overall counts of users, markdowns, and other system entities.
25
+
26
+ 3. raw_db_query: Execute raw MongoDB queries for database exploration.
27
+ - Parameters:
28
+ - queryType (required): The type of raw query to execute
29
+ • listDatabases: List all databases (requires admin access)
30
+ • listCollections: List all collections in a database
31
+ • countDocuments: Count documents in a collection
32
+ • findOne: Find a single document in a collection
33
+ • aggregate: Run aggregation pipeline
34
+ • adminCommand: Execute admin commands
35
+ - database (optional): Database name (defaults to current database)
36
+ - collection (required for collection queries): Collection name
37
+ - filter (optional): MongoDB filter/query object. Can be:
38
+ • A JSON object: { createdAt: { $gte: new Date() } }
39
+ • A JSON string: '{"createdAt": {"$gte": {"$date": "2024-01-01"}}}'
40
+ • For aggregate: an array of pipeline stages as object or JSON string
41
+ - limit (optional): Limit results (default: 10)
42
+ - adminCommand (optional): Admin command for adminCommand queryType (as object or JSON string)
43
+ - Usage: Use this to discover collection names, databases, or run admin commands.
44
+ - IMPORTANT: For complex queries, use JSON string format to avoid parsing issues
45
+
46
+ IMPORTANT ERROR HANDLING INSTRUCTIONS:
47
+ - When a tool returns an error, it will be in structured JSON format with error details
48
+ - ALWAYS provide a friendly, conversational response to the user about tool errors
49
+ - NEVER show raw error JSON to users
50
+ - DO: "I had trouble accessing the database. Let me try a different approach..."
51
+ - DO NOT: Show the actual error JSON to users
52
+ - Extract the error message and provide helpful suggestions based on the error context
53
+ - If an error is not recoverable, explain why and suggest alternatives
54
+ - If an error is recoverable, explain what you'll try next
55
+ - Use the error suggestions provided in the tool response to inform your response
56
+
57
+ INSTRUCTIONS:
58
+ - Always use tools when you need actual data from the database
59
+ - Never make up data or statistics
60
+ - For database queries, use exact model names as they appear in the system
61
+ - When using query_database, construct appropriate MongoDB query objects based on the user's request
62
+ - If you don't have enough information for a query, ask clarifying questions
63
+ - Use get_system_stats for high-level overview requests
64
+ - Use raw_db_query for:
65
+ * Discovering what collections exist: queryType: "listCollections"
66
+ * Finding database names: queryType: "listDatabases" (may require admin)
67
+ * Counting documents: queryType: "countDocuments" with collection and filter
68
+ * Exploring collection structure: queryType: "findOne" or "aggregate"
69
+ - For specific records, use query_database with appropriate filters
70
+
71
+ Respond helpfully and only use the tools when necessary for accurate information. Always provide friendly error messages to users when tools fail.`
72
+ },
73
+ providerKey: {
74
+ type: String,
75
+ required: true
76
+ },
77
+ model: {
78
+ type: String,
79
+ required: true
80
+ },
81
+ tools: {
82
+ type: [String],
83
+ default: []
84
+ },
85
+ temperature: {
86
+ type: Number,
87
+ default: 0.7
88
+ },
89
+ maxIterations: {
90
+ type: Number,
91
+ default: 10
92
+ },
93
+ orgId: {
94
+ type: mongoose.Schema.Types.ObjectId,
95
+ ref: 'Organization'
96
+ },
97
+ ownerUserId: {
98
+ type: mongoose.Schema.Types.ObjectId,
99
+ ref: 'User'
100
+ }
101
+ }, {
102
+ timestamps: true
103
+ });
104
+
105
+ module.exports = mongoose.model('Agent', agentSchema);
@@ -0,0 +1,82 @@
1
+ const mongoose = require('mongoose');
2
+ const { ObjectId } = mongoose.Schema.Types;
3
+
4
+ const agentMessageSchema = new mongoose.Schema({
5
+ agentId: {
6
+ type: ObjectId,
7
+ ref: 'Agent',
8
+ required: true,
9
+ index: true
10
+ },
11
+ chatId: {
12
+ type: String,
13
+ required: true,
14
+ index: true
15
+ },
16
+ role: {
17
+ type: String,
18
+ enum: ['user', 'assistant', 'system', 'tool'],
19
+ required: true
20
+ },
21
+ content: {
22
+ type: String,
23
+ required: function() {
24
+ // Content is not required if:
25
+ // 1. Role is 'tool' (content might be in metadata or implied) - though usually tool has content
26
+ // 2. Role is 'assistant' AND it has toolCalls (OpenAI often returns null content with tool calls)
27
+ if (this.role === 'tool') return false;
28
+ if (this.role === 'assistant' && this.toolCalls && this.toolCalls.length > 0) return false;
29
+ return true;
30
+ }
31
+ },
32
+ toolCalls: [{
33
+ name: String,
34
+ arguments: mongoose.Schema.Types.Mixed,
35
+ toolCallId: String
36
+ }],
37
+ toolCallId: {
38
+ type: String,
39
+ index: true
40
+ },
41
+ metadata: {
42
+ tokens: Number,
43
+ processingTime: Number,
44
+ model: String,
45
+ provider: String,
46
+ timestamp: Date,
47
+ temperature: Number
48
+ },
49
+ createdAt: {
50
+ type: Date,
51
+ default: Date.now,
52
+ index: true
53
+ },
54
+ updatedAt: {
55
+ type: Date,
56
+ default: Date.now
57
+ }
58
+ }, {
59
+ timestamps: true,
60
+ toJSON: { virtuals: true },
61
+ toObject: { virtuals: true }
62
+ });
63
+
64
+ // Compound index for efficient session history retrieval
65
+ agentMessageSchema.index(
66
+ { agentId: 1, chatId: 1, createdAt: 1 },
67
+ { name: 'session_history_idx' }
68
+ );
69
+
70
+ // Index for tool call lookup
71
+ agentMessageSchema.index(
72
+ { toolCallId: 1 },
73
+ { name: 'tool_call_idx' }
74
+ );
75
+
76
+ // Index for searching content
77
+ agentMessageSchema.index(
78
+ { content: 'text' },
79
+ { name: 'content_search_idx' }
80
+ );
81
+
82
+ module.exports = mongoose.model('AgentMessage', agentMessageSchema);
@@ -10,7 +10,7 @@ const cacheEntrySchema = new mongoose.Schema(
10
10
 
11
11
  sizeBytes: { type: Number, default: 0 },
12
12
 
13
- expiresAt: { type: Date, default: null, index: true },
13
+ expiresAt: { type: Date, default: null },
14
14
 
15
15
  hits: { type: Number, default: 0 },
16
16
  lastAccessAt: { type: Date, default: null },
@@ -12,7 +12,7 @@ const consoleLogSchema = new mongoose.Schema(
12
12
  requestId: { type: String, default: '', index: true },
13
13
 
14
14
  createdAt: { type: Date, default: Date.now, index: true },
15
- expiresAt: { type: Date, default: null, index: true },
15
+ expiresAt: { type: Date, default: null },
16
16
  },
17
17
  { timestamps: false, collection: 'console_logs' },
18
18
  );
@@ -0,0 +1,75 @@
1
+ const mongoose = require('mongoose');
2
+
3
+ const experimentVariantSchema = new mongoose.Schema(
4
+ {
5
+ key: { type: String, required: true },
6
+ weight: { type: Number, default: 0 },
7
+ configSlug: { type: String, default: '' },
8
+ },
9
+ { _id: false },
10
+ );
11
+
12
+ const metricDefinitionSchema = new mongoose.Schema(
13
+ {
14
+ key: { type: String, required: true },
15
+ kind: { type: String, enum: ['count', 'sum', 'avg', 'rate'], default: 'count' },
16
+ numeratorEventKey: { type: String, default: '' },
17
+ denominatorEventKey: { type: String, default: '' },
18
+ objective: { type: String, enum: ['maximize', 'minimize'], default: 'maximize' },
19
+ },
20
+ { _id: false },
21
+ );
22
+
23
+ const winnerPolicySchema = new mongoose.Schema(
24
+ {
25
+ mode: { type: String, enum: ['manual', 'automatic'], default: 'manual' },
26
+ pickAfterMs: { type: Number, default: 0 },
27
+ minAssignments: { type: Number, default: 0 },
28
+ minExposures: { type: Number, default: 0 },
29
+ minConversions: { type: Number, default: 0 },
30
+ statMethod: { type: String, enum: ['simple_rate', 'bayesian_beta'], default: 'simple_rate' },
31
+ overrideWinnerVariantKey: { type: String, default: '' },
32
+ },
33
+ { _id: false },
34
+ );
35
+
36
+ const experimentSchema = new mongoose.Schema(
37
+ {
38
+ organizationId: { type: mongoose.Schema.Types.ObjectId, ref: 'Organization', default: null, index: true },
39
+
40
+ code: { type: String, required: true },
41
+ name: { type: String, default: '' },
42
+ description: { type: String, default: '' },
43
+
44
+ status: { type: String, enum: ['draft', 'running', 'paused', 'completed'], default: 'draft', index: true },
45
+
46
+ startedAt: { type: Date, default: null },
47
+ endsAt: { type: Date, default: null },
48
+
49
+ assignment: {
50
+ unit: { type: String, enum: ['subjectId'], default: 'subjectId' },
51
+ sticky: { type: Boolean, default: true },
52
+ salt: { type: String, default: '' },
53
+ },
54
+
55
+ variants: { type: [experimentVariantSchema], default: [] },
56
+
57
+ primaryMetric: { type: metricDefinitionSchema, required: true },
58
+ secondaryMetrics: { type: [metricDefinitionSchema], default: [] },
59
+
60
+ winnerPolicy: { type: winnerPolicySchema, default: () => ({}) },
61
+
62
+ winnerVariantKey: { type: String, default: '' },
63
+ winnerDecidedAt: { type: Date, default: null },
64
+ winnerReason: { type: String, default: '' },
65
+
66
+ createdByUserId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null },
67
+ updatedByUserId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', default: null },
68
+ },
69
+ { timestamps: true, collection: 'experiments' },
70
+ );
71
+
72
+ experimentSchema.index({ organizationId: 1, code: 1 }, { unique: true });
73
+ experimentSchema.index({ status: 1, startedAt: 1 });
74
+
75
+ module.exports = mongoose.models.Experiment || mongoose.model('Experiment', experimentSchema);