@oneuptime/common 11.3.26 → 11.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (298) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
  3. package/Models/DatabaseModels/IncidentMember.ts +1 -1
  4. package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
  5. package/Models/DatabaseModels/Index.ts +3 -0
  6. package/Models/DatabaseModels/MigrationFailure.ts +170 -0
  7. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
  8. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
  9. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
  10. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
  11. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
  12. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
  13. package/Models/DatabaseModels/Project.ts +60 -0
  14. package/Server/API/AIInvestigationAPI.ts +148 -0
  15. package/Server/API/AlertAPI.ts +1 -1
  16. package/Server/API/IncidentAPI.ts +2 -2
  17. package/Server/API/IncidentEpisodeAPI.ts +1 -1
  18. package/Server/API/LlmProviderAPI.ts +1 -0
  19. package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  28. package/Server/Infrastructure/PostgresDatabase.ts +11 -0
  29. package/Server/Services/AIService.ts +74 -2
  30. package/Server/Services/AlertService.ts +25 -0
  31. package/Server/Services/AnalyticsDatabaseService.ts +10 -1
  32. package/Server/Services/DatabaseService.ts +10 -1
  33. package/Server/Services/IncidentService.ts +26 -1
  34. package/Server/Services/IncidentStateTimelineService.ts +19 -0
  35. package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
  36. package/Server/Services/IncomingCallPolicyService.ts +42 -1
  37. package/Server/Services/Index.ts +2 -0
  38. package/Server/Services/MigrationFailureService.ts +9 -0
  39. package/Server/Services/MonitorProbeService.ts +33 -7
  40. package/Server/Services/MonitorService.ts +19 -7
  41. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  42. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
  43. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
  44. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
  45. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
  46. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
  47. package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
  48. package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
  49. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
  50. package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
  51. package/Server/Services/TeamMemberService.ts +6 -0
  52. package/Server/Services/UserNotificationRuleService.ts +13 -42
  53. package/Server/Services/UserOnCallLogService.ts +90 -0
  54. package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
  55. package/Server/Types/Database/QueryUtil.ts +57 -0
  56. package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
  57. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
  58. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
  59. package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
  60. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
  61. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
  62. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
  63. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
  64. package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
  65. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
  66. package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
  67. package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
  68. package/Server/Utils/AI/Toolbox/Index.ts +15 -1
  69. package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
  70. package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
  71. package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
  72. package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
  73. package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
  74. package/Server/Utils/LLM/LLMService.ts +110 -11
  75. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
  76. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
  77. package/Tests/Server/API/BaseAPI.test.ts +1 -0
  78. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
  79. package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
  80. package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
  81. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
  82. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
  83. package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
  84. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
  85. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
  86. package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
  87. package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
  88. package/Tests/Types/Events/Recurring.test.ts +22 -9
  89. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
  90. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
  91. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
  92. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
  93. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
  94. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
  95. package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
  96. package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
  97. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
  98. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
  99. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
  100. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
  101. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
  102. package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
  103. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
  104. package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
  105. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
  106. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
  107. package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
  108. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
  109. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
  110. package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
  111. package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
  112. package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
  113. package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
  114. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
  115. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
  116. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
  117. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
  118. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
  119. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
  120. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
  121. package/Types/Date.ts +246 -15
  122. package/Types/OnCallDutyPolicy/Layer.ts +790 -260
  123. package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
  124. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
  125. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
  126. package/UI/Components/Calendar/Calendar.tsx +8 -2
  127. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  128. package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
  129. package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
  130. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
  131. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  132. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
  135. package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
  136. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  137. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
  138. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  139. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  140. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  141. package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
  142. package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
  143. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
  144. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
  146. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
  148. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
  150. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
  152. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
  154. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  156. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  157. package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
  158. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
  159. package/build/dist/Server/API/AlertAPI.js +1 -1
  160. package/build/dist/Server/API/IncidentAPI.js +2 -2
  161. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  162. package/build/dist/Server/API/LlmProviderAPI.js +1 -0
  163. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  164. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  181. package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
  182. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  183. package/build/dist/Server/Services/AIService.js +53 -2
  184. package/build/dist/Server/Services/AIService.js.map +1 -1
  185. package/build/dist/Server/Services/AlertService.js +24 -0
  186. package/build/dist/Server/Services/AlertService.js.map +1 -1
  187. package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
  188. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  189. package/build/dist/Server/Services/DatabaseService.js +10 -1
  190. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  191. package/build/dist/Server/Services/IncidentService.js +25 -1
  192. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  193. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  194. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  195. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
  196. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
  197. package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
  198. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  199. package/build/dist/Server/Services/Index.js +2 -0
  200. package/build/dist/Server/Services/Index.js.map +1 -1
  201. package/build/dist/Server/Services/MigrationFailureService.js +9 -0
  202. package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
  203. package/build/dist/Server/Services/MonitorProbeService.js +33 -7
  204. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  205. package/build/dist/Server/Services/MonitorService.js +12 -5
  206. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
  210. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  211. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
  212. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  213. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
  214. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  215. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
  216. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  217. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
  218. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  219. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
  220. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  221. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
  222. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
  223. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
  224. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  225. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
  226. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  227. package/build/dist/Server/Services/TeamMemberService.js +6 -0
  228. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  229. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
  230. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  231. package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
  232. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  233. package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
  234. package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
  235. package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
  236. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  237. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
  238. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  239. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
  240. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  241. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
  242. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  243. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
  244. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  245. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
  246. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  247. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
  248. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
  249. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
  250. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
  251. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
  252. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
  253. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
  254. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
  255. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
  256. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
  257. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
  258. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
  259. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
  260. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/Toolbox/Index.js +10 -1
  262. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
  264. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
  265. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
  266. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
  267. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
  268. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  269. package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
  270. package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
  271. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
  272. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
  273. package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
  274. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  275. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
  276. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  277. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
  278. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  279. package/build/dist/Types/Date.js +185 -15
  280. package/build/dist/Types/Date.js.map +1 -1
  281. package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
  282. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  283. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
  284. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
  285. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
  286. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
  287. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
  288. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  289. package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
  290. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  291. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  292. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  293. package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
  294. package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
  295. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  296. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
  297. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  298. package/package.json +1 -1
@@ -0,0 +1,178 @@
1
+ import ObjectID from "../../../../Types/ObjectID";
2
+ import OneUptimeDate from "../../../../Types/Date";
3
+ import { Blue500 } from "../../../../Types/BrandColors";
4
+ import { IncidentFeedEventType } from "../../../../Models/DatabaseModels/IncidentFeed";
5
+ import IncidentFeedService from "../../../Services/IncidentFeedService";
6
+ import IncidentAIContextBuilder, {
7
+ IncidentContextData,
8
+ } from "../IncidentAIContextBuilder";
9
+ import SentinelInvestigationEngine from "./SentinelInvestigationEngine";
10
+ import SentinelMemory from "./SentinelMemory";
11
+ import { ObservabilityAssistantResult } from "../Chat/ObservabilityAssistant";
12
+ import logger from "../../Logger";
13
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
14
+
15
+ /*
16
+ * Sentinel — incident investigation.
17
+ *
18
+ * When a new incident is declared, wakes Sentinel to investigate it (read-only)
19
+ * and post a cited root-cause analysis into the incident timeline + Slack/Teams
20
+ * — before the on-call engineer has finished reading the page. The heavy lifting
21
+ * (run lifecycle, the agent loop, confidence gating) lives in the shared
22
+ * SentinelInvestigationEngine; this file only assembles incident context and
23
+ * posts the result to the incident's feed.
24
+ *
25
+ * Built on the fire-and-forget async pattern already used throughout
26
+ * IncidentService.onCreateSuccess. See Internal/Roadmap/AISentinelReliabilityBrain.md.
27
+ */
28
+ export default class SentinelIncidentInvestigationRunner {
29
+ /*
30
+ * Entry point called (fire-and-forget) from IncidentService.onCreateSuccess.
31
+ * Never throws — all failures are logged and recorded on the AIRun.
32
+ */
33
+ @CaptureSpan()
34
+ public static async investigateNewIncident(data: {
35
+ incidentId: ObjectID;
36
+ projectId: ObjectID;
37
+ }): Promise<void> {
38
+ const { incidentId, projectId } = data;
39
+
40
+ try {
41
+ if (
42
+ !(await SentinelInvestigationEngine.isEnabledForProject(
43
+ projectId,
44
+ "Incident",
45
+ ))
46
+ ) {
47
+ return;
48
+ }
49
+
50
+ const contextData: IncidentContextData =
51
+ await IncidentAIContextBuilder.buildIncidentContext({
52
+ incidentId,
53
+ includeWorkspaceMessages: false,
54
+ });
55
+
56
+ /*
57
+ * Recurrence memory: pull in past resolved incidents that share this
58
+ * incident's monitors/labels so Sentinel can recognise "I've seen this
59
+ * before" and reference the prior fix.
60
+ */
61
+ const priorCasesContext: string =
62
+ await SentinelMemory.getPriorSimilarIncidentsContext({
63
+ projectId,
64
+ currentIncidentId: incidentId,
65
+ monitorNames: (contextData.incident.monitors || [])
66
+ .map((m: { name?: string }) => {
67
+ return m.name || "";
68
+ })
69
+ .filter(Boolean),
70
+ labelNames: (contextData.incident.labels || [])
71
+ .map((l: { name?: string }) => {
72
+ return l.name || "";
73
+ })
74
+ .filter(Boolean),
75
+ });
76
+
77
+ await SentinelInvestigationEngine.investigate({
78
+ projectId,
79
+ feature: "Sentinel Incident Investigation",
80
+ subjectIncidentId: incidentId,
81
+ contextSummary:
82
+ this.buildIncidentSummary(contextData) + priorCasesContext,
83
+ postAnalysis: async (postData: {
84
+ analysisMarkdown: string;
85
+ isConfident: boolean;
86
+ result: ObservabilityAssistantResult;
87
+ }): Promise<void> => {
88
+ await IncidentFeedService.createIncidentFeedItem({
89
+ incidentId,
90
+ projectId,
91
+ incidentFeedEventType: IncidentFeedEventType.RootCause,
92
+ displayColor: Blue500,
93
+ feedInfoInMarkdown: postData.analysisMarkdown,
94
+ /*
95
+ * Quiet mode: an inconclusive investigation posts to the timeline
96
+ * but does NOT loudly ping the workspace / on-call.
97
+ */
98
+ workspaceNotification: {
99
+ sendWorkspaceNotification: postData.isConfident,
100
+ },
101
+ });
102
+ },
103
+ });
104
+ } catch (error) {
105
+ logger.error(
106
+ `Sentinel: unexpected error investigating incident ${incidentId.toString()}: ${error}`,
107
+ );
108
+ }
109
+ }
110
+
111
+ // Build a compact incident record to seed the investigation.
112
+ private static buildIncidentSummary(
113
+ contextData: IncidentContextData,
114
+ ): string {
115
+ const { incident, internalNotes } = contextData;
116
+
117
+ const lines: Array<string> = [];
118
+ lines.push("# Incident");
119
+ lines.push(`Title: ${incident.title || "N/A"}`);
120
+
121
+ if (incident.description) {
122
+ lines.push(`Description: ${incident.description}`);
123
+ }
124
+
125
+ lines.push(`Severity: ${incident.incidentSeverity?.name || "N/A"}`);
126
+ lines.push(
127
+ `Current state: ${incident.currentIncidentState?.name || "N/A"}`,
128
+ );
129
+ lines.push(
130
+ `Declared at: ${
131
+ incident.createdAt
132
+ ? OneUptimeDate.getDateAsFormattedString(incident.createdAt)
133
+ : "N/A"
134
+ }`,
135
+ );
136
+
137
+ if (incident.monitors && incident.monitors.length > 0) {
138
+ lines.push(
139
+ `Affected monitors: ${incident.monitors
140
+ .map((m: { name?: string }) => {
141
+ return m.name;
142
+ })
143
+ .filter(Boolean)
144
+ .join(", ")}`,
145
+ );
146
+ }
147
+
148
+ if (incident.labels && incident.labels.length > 0) {
149
+ lines.push(
150
+ `Labels: ${incident.labels
151
+ .map((l: { name?: string }) => {
152
+ return l.name;
153
+ })
154
+ .filter(Boolean)
155
+ .join(", ")}`,
156
+ );
157
+ }
158
+
159
+ if (incident.rootCause) {
160
+ lines.push(`Root cause (as recorded so far): ${incident.rootCause}`);
161
+ }
162
+
163
+ const recentNotes: Array<string> = (internalNotes || [])
164
+ .slice(-5)
165
+ .map((note: { note?: string }) => {
166
+ return note.note ? `- ${note.note}` : "";
167
+ })
168
+ .filter(Boolean);
169
+
170
+ if (recentNotes.length > 0) {
171
+ lines.push("");
172
+ lines.push("Recent internal notes:");
173
+ lines.push(...recentNotes);
174
+ }
175
+
176
+ return lines.join("\n");
177
+ }
178
+ }
@@ -0,0 +1,105 @@
1
+ import ObjectID from "../../../../Types/ObjectID";
2
+ import { Blue500 } from "../../../../Types/BrandColors";
3
+ import Incident from "../../../../Models/DatabaseModels/Incident";
4
+ import { IncidentFeedEventType } from "../../../../Models/DatabaseModels/IncidentFeed";
5
+ import IncidentService from "../../../Services/IncidentService";
6
+ import IncidentFeedService from "../../../Services/IncidentFeedService";
7
+ import SentinelInvestigationEngine from "./SentinelInvestigationEngine";
8
+ import logger from "../../Logger";
9
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
10
+
11
+ /*
12
+ * Sentinel — auto-draft postmortem on resolve (Phase 3, "close the loop").
13
+ *
14
+ * When an incident moves to a resolved state, Sentinel drafts a postmortem from
15
+ * the incident timeline + telemetry (reusing the existing
16
+ * IncidentService.generatePostmortemFromAI) and saves it to the incident for a
17
+ * human to review and edit — turning a ~90-minute manual writeup into a review.
18
+ *
19
+ * It NEVER overwrites a postmortem that already exists (human work wins), is
20
+ * gated by the same per-project opt-in + LLM provider as investigations, and is
21
+ * fire-and-forget: failures are logged, never surfaced to the resolve flow.
22
+ */
23
+ const MAX_FEED_PREVIEW_CHARS: number = 6000;
24
+
25
+ export default class SentinelIncidentPostmortemRunner {
26
+ @CaptureSpan()
27
+ public static async draftPostmortemOnResolve(data: {
28
+ incidentId: ObjectID;
29
+ projectId: ObjectID;
30
+ }): Promise<void> {
31
+ const { incidentId, projectId } = data;
32
+
33
+ try {
34
+ if (
35
+ !(await SentinelInvestigationEngine.isEnabledForProject(
36
+ projectId,
37
+ "Incident",
38
+ ))
39
+ ) {
40
+ return;
41
+ }
42
+
43
+ const incident: Incident | null = await IncidentService.findOneById({
44
+ id: incidentId,
45
+ select: {
46
+ _id: true,
47
+ incidentNumber: true,
48
+ postmortemNote: true,
49
+ },
50
+ props: { isRoot: true },
51
+ });
52
+
53
+ if (!incident) {
54
+ return;
55
+ }
56
+
57
+ // Never clobber an existing postmortem (human or previously drafted).
58
+ if (
59
+ incident.postmortemNote &&
60
+ incident.postmortemNote.trim().length > 0
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ const draft: string = await IncidentService.generatePostmortemFromAI({
66
+ incidentId,
67
+ });
68
+
69
+ if (!draft || !draft.trim()) {
70
+ return;
71
+ }
72
+
73
+ await IncidentService.updateOneById({
74
+ id: incidentId,
75
+ data: {
76
+ postmortemNote: draft,
77
+ },
78
+ props: { isRoot: true },
79
+ });
80
+
81
+ await IncidentFeedService.createIncidentFeedItem({
82
+ incidentId,
83
+ projectId,
84
+ incidentFeedEventType: IncidentFeedEventType.PostmortemNote,
85
+ displayColor: Blue500,
86
+ feedInfoInMarkdown: `## 🧠 Sentinel — Draft Postmortem\n\nSentinel drafted a postmortem for incident #${incident.incidentNumber} from the incident timeline and telemetry. It has been saved on the incident for you to review and edit.`,
87
+ moreInformationInMarkdown:
88
+ draft.length > MAX_FEED_PREVIEW_CHARS
89
+ ? `${draft.substring(0, MAX_FEED_PREVIEW_CHARS)}\n\n…(truncated — the full draft is saved on the incident.)`
90
+ : draft,
91
+ workspaceNotification: {
92
+ sendWorkspaceNotification: true,
93
+ },
94
+ });
95
+
96
+ logger.debug(
97
+ `Sentinel: drafted postmortem for incident ${incidentId.toString()}.`,
98
+ );
99
+ } catch (error) {
100
+ logger.error(
101
+ `Sentinel: failed to draft postmortem for incident ${incidentId.toString()}: ${error}`,
102
+ );
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,405 @@
1
+ import ObjectID from "../../../../Types/ObjectID";
2
+ import OneUptimeDate from "../../../../Types/Date";
3
+ import { JSONObject } from "../../../../Types/JSON";
4
+ import {
5
+ AIChatCitation,
6
+ AIRunEventResultSummary,
7
+ } from "../../../../Types/AI/AIChatTypes";
8
+ import AIRunType from "../../../../Types/AI/AIRunType";
9
+ import AIRunStatus from "../../../../Types/AI/AIRunStatus";
10
+ import AIRunEventType from "../../../../Types/AI/AIRunEventType";
11
+ import AIRun from "../../../../Models/DatabaseModels/AIRun";
12
+ import AIRunEvent from "../../../../Models/DatabaseModels/AIRunEvent";
13
+ import Project from "../../../../Models/DatabaseModels/Project";
14
+ import LlmProvider from "../../../../Models/DatabaseModels/LlmProvider";
15
+ import AIRunService from "../../../Services/AIRunService";
16
+ import AIRunEventService from "../../../Services/AIRunEventService";
17
+ import ProjectService from "../../../Services/ProjectService";
18
+ import LlmProviderService from "../../../Services/LlmProviderService";
19
+ import ObservabilityAssistant, {
20
+ ObservabilityAssistantResult,
21
+ ObservabilityAssistantStep,
22
+ ObservabilityAssistantStepType,
23
+ } from "../Chat/ObservabilityAssistant";
24
+ import logger from "../../Logger";
25
+ import CaptureSpan from "../../Telemetry/CaptureSpan";
26
+
27
+ /*
28
+ * Sentinel — the shared autonomous-investigation engine.
29
+ *
30
+ * A single subject-agnostic core that powers "wake on signal" investigations
31
+ * for both incidents and alerts (and, later, anomalies). It:
32
+ * 1. records the run as an AIRun(Investigation) + AIRunEvents (audit trail),
33
+ * 2. runs the existing read-only, tool-grounded, citation-minting agent loop
34
+ * (ObservabilityAssistant) with the Sentinel persona and a larger budget,
35
+ * 3. brands the cited analysis, judges confidence, and
36
+ * 4. hands the finished analysis back to the caller to post to the subject's
37
+ * timeline.
38
+ *
39
+ * The investigation is strictly READ-ONLY: it can never mutate anything.
40
+ * Enablement + provider gating is shared via isEnabledForProject().
41
+ */
42
+
43
+ // Budgets — larger than an interactive chat-ops answer, small enough to stay cheap.
44
+ const MAX_LLM_CALLS: number = 8;
45
+ const MAX_TOOL_CALLS: number = 12;
46
+ const MAX_WALL_CLOCK_MS: number = 150 * 1000;
47
+ const MAX_OUTPUT_TOKENS: number = 2000;
48
+
49
+ /*
50
+ * Sentinel's own contract: it writes exactly this phrase when it cannot
51
+ * determine a cause. We match it deterministically to drive "quiet mode" —
52
+ * a non-answer should never loudly page the on-call.
53
+ */
54
+ const INCONCLUSIVE_RE: RegExp = /inconclusive[^.\n]*insufficient signal/i;
55
+
56
+ // Maps a live agent step to the AIRunEvent type persisted for the glass-box trail.
57
+ const STEP_EVENT_TYPE: Record<ObservabilityAssistantStepType, AIRunEventType> =
58
+ {
59
+ llm_started: AIRunEventType.LlmCallStarted,
60
+ llm_completed: AIRunEventType.LlmCallCompleted,
61
+ tool_started: AIRunEventType.ToolCallStarted,
62
+ tool_completed: AIRunEventType.ToolCallCompleted,
63
+ tool_failed: AIRunEventType.ToolCallFailed,
64
+ };
65
+
66
+ const INVESTIGATION_PERSONA: string = `You are "Sentinel", OneUptime's autonomous AI Site Reliability Engineer. You have been woken automatically because a NEW signal (an incident or alert) was just declared in this project — no human has asked you a question yet. Investigate it proactively and produce a first-pass root cause analysis that the on-call engineer will read the moment they are paged.
67
+
68
+ Investigate like a senior on-call engineer:
69
+ - Start from the affected monitors/services named in the signal.
70
+ - Use your read tools to inspect the telemetry AROUND the signal's creation time: recent exceptions and their trends, error/latency metrics versus their normal range, failing traces, relevant logs, and recent changes / deploys.
71
+ - Form the single most likely root-cause hypothesis. If the evidence is inconclusive, say so plainly and list what you checked — do NOT guess a cause the data does not support.
72
+ - If the context lists past resolved incidents, check whether this is a RECURRENCE. If the current signal matches one, say so explicitly, reference that incident number, and note how it was resolved before — but still verify against the current telemetry.
73
+
74
+ Write your final answer as a concise incident-response note with exactly these sections (use these markdown headings):
75
+ **Summary** — one or two sentences a paged engineer can read in five seconds.
76
+ **Most likely root cause** — your hypothesis, with only the confidence the evidence actually supports, each factual claim carrying its [C#] citation. If you could not determine it, write "Inconclusive — insufficient signal" and explain what is missing.
77
+ **Evidence** — the key findings that support or rule out the hypothesis, each cited.
78
+ **Suggested next steps** — concrete actions for the on-call engineer.
79
+
80
+ Keep it tight and skimmable. You are read-only: never claim to have changed anything.`;
81
+
82
+ // Which signal an investigation is about — each has its own per-project opt-in.
83
+ export type SentinelSubjectType = "Incident" | "Alert";
84
+
85
+ export interface InvestigationRequest {
86
+ projectId: ObjectID;
87
+ // Label recorded on LlmLog, e.g. "Sentinel Incident Investigation".
88
+ feature: string;
89
+ // A compact markdown summary of the subject that seeds the investigation.
90
+ contextSummary: string;
91
+ /*
92
+ * The subject that triggered this run — links the AIRun so the live panel can
93
+ * find "the investigation for this incident/alert". Exactly one is set.
94
+ */
95
+ subjectIncidentId?: ObjectID | undefined;
96
+ subjectAlertId?: ObjectID | undefined;
97
+ /*
98
+ * Called with the finished, branded, cited analysis so the caller can post it
99
+ * to the subject's timeline. `isConfident` is false when Sentinel reported it
100
+ * could not determine the cause — callers use it to post QUIETLY (no loud
101
+ * workspace ping) rather than paging a non-answer.
102
+ */
103
+ postAnalysis: (data: {
104
+ analysisMarkdown: string;
105
+ isConfident: boolean;
106
+ result: ObservabilityAssistantResult;
107
+ }) => Promise<void>;
108
+ }
109
+
110
+ export default class SentinelInvestigationEngine {
111
+ /*
112
+ * Shared gate: AI enabled, the subject's auto-investigation opt-in on, and an
113
+ * LLM provider configured. Incidents and alerts each have their own opt-in so
114
+ * they can be enabled independently. Runs before any (subject-specific)
115
+ * context assembly.
116
+ */
117
+ @CaptureSpan()
118
+ public static async isEnabledForProject(
119
+ projectId: ObjectID,
120
+ subjectType: SentinelSubjectType,
121
+ ): Promise<boolean> {
122
+ const project: Project | null = await ProjectService.findOneById({
123
+ id: projectId,
124
+ select: {
125
+ enableAi: true,
126
+ enableAutomaticIncidentInvestigation: true,
127
+ enableAutomaticAlertInvestigation: true,
128
+ },
129
+ props: { isRoot: true },
130
+ });
131
+
132
+ if (!project) {
133
+ return false;
134
+ }
135
+
136
+ if (project.enableAi === false) {
137
+ return false;
138
+ }
139
+
140
+ const isOptedIn: boolean =
141
+ subjectType === "Alert"
142
+ ? project.enableAutomaticAlertInvestigation === true
143
+ : project.enableAutomaticIncidentInvestigation === true;
144
+
145
+ if (!isOptedIn) {
146
+ return false;
147
+ }
148
+
149
+ const llmProvider: LlmProvider | null =
150
+ await LlmProviderService.getLLMProviderForProject(projectId);
151
+
152
+ if (!llmProvider) {
153
+ logger.debug(
154
+ `Sentinel: skipping investigation for project ${projectId.toString()} — no LLM provider configured.`,
155
+ );
156
+ return false;
157
+ }
158
+
159
+ return true;
160
+ }
161
+
162
+ /*
163
+ * Run one investigation end-to-end. Never throws — failures are logged and
164
+ * recorded on the AIRun.
165
+ */
166
+ @CaptureSpan()
167
+ public static async investigate(
168
+ request: InvestigationRequest,
169
+ ): Promise<void> {
170
+ const { projectId } = request;
171
+
172
+ // Record the run up front so it is auditable even if it fails.
173
+ const run: AIRun = new AIRun();
174
+ run.projectId = projectId;
175
+ run.runType = AIRunType.Investigation;
176
+ run.status = AIRunStatus.Running;
177
+ run.startedAt = OneUptimeDate.getCurrentDate();
178
+ run.lastHeartbeatAt = OneUptimeDate.getCurrentDate();
179
+
180
+ if (request.subjectIncidentId) {
181
+ run.triggeredByIncidentId = request.subjectIncidentId;
182
+ }
183
+ if (request.subjectAlertId) {
184
+ run.triggeredByAlertId = request.subjectAlertId;
185
+ }
186
+
187
+ let createdRun: AIRun;
188
+ try {
189
+ createdRun = await AIRunService.create({
190
+ data: run,
191
+ props: { isRoot: true },
192
+ });
193
+ } catch (error) {
194
+ logger.error(`Sentinel: failed to create investigation run: ${error}`);
195
+ return;
196
+ }
197
+
198
+ const aiRunId: ObjectID = createdRun.id!;
199
+ let sequence: number = 0;
200
+
201
+ await this.emitEvent({
202
+ projectId,
203
+ aiRunId,
204
+ sequence: sequence++,
205
+ eventType: AIRunEventType.RunStarted,
206
+ });
207
+
208
+ /*
209
+ * Live narration: persist each LLM/tool step as an AIRunEvent so the UI can
210
+ * "watch it think" by polling the run's events. Best-effort, ordered.
211
+ */
212
+ const onStep: (step: ObservabilityAssistantStep) => Promise<void> = async (
213
+ step: ObservabilityAssistantStep,
214
+ ): Promise<void> => {
215
+ const resultSummary: AIRunEventResultSummary | undefined =
216
+ step.rowCount !== undefined ||
217
+ step.durationMs !== undefined ||
218
+ step.errorMessage !== undefined
219
+ ? {
220
+ rowCount: step.rowCount,
221
+ durationInMs: step.durationMs,
222
+ errorMessage: step.errorMessage,
223
+ }
224
+ : undefined;
225
+
226
+ await this.emitEvent({
227
+ projectId,
228
+ aiRunId,
229
+ sequence: sequence++,
230
+ eventType: STEP_EVENT_TYPE[step.type],
231
+ toolName: step.toolName,
232
+ toolArguments: step.toolArguments,
233
+ resultSummary,
234
+ citationId: step.citationId,
235
+ });
236
+
237
+ // Keep the run visibly alive for the stale-run sweeper + live UI.
238
+ if (step.type === "llm_started") {
239
+ await this.touchHeartbeat(aiRunId);
240
+ }
241
+ };
242
+
243
+ try {
244
+ const result: ObservabilityAssistantResult =
245
+ await ObservabilityAssistant.answerQuestion({
246
+ projectId,
247
+ // System run — full read access to the project's telemetry.
248
+ props: { isRoot: true },
249
+ feature: request.feature,
250
+ systemInstructions: INVESTIGATION_PERSONA,
251
+ question: `A new signal has just been declared and you have been woken to investigate it. Investigate now and produce your root cause analysis.\n\n${request.contextSummary}`,
252
+ maxLlmCalls: MAX_LLM_CALLS,
253
+ maxToolCalls: MAX_TOOL_CALLS,
254
+ maxWallClockMs: MAX_WALL_CLOCK_MS,
255
+ maxOutputTokens: MAX_OUTPUT_TOKENS,
256
+ onStep,
257
+ });
258
+
259
+ await AIRunService.updateOneBy({
260
+ query: { _id: aiRunId.toString(), status: AIRunStatus.Running },
261
+ data: {
262
+ status: AIRunStatus.Completed,
263
+ completedAt: OneUptimeDate.getCurrentDate(),
264
+ lastHeartbeatAt: OneUptimeDate.getCurrentDate(),
265
+ llmCallCount: result.llmCallCount,
266
+ toolCallCount: result.toolCallCount,
267
+ totalTokens: result.totalTokens,
268
+ } as never,
269
+ props: { isRoot: true },
270
+ });
271
+
272
+ await this.emitEvent({
273
+ projectId,
274
+ aiRunId,
275
+ sequence: sequence++,
276
+ eventType: AIRunEventType.RunCompleted,
277
+ });
278
+
279
+ const analysis: string = (result.contentInMarkdown || "").trim();
280
+
281
+ if (!analysis) {
282
+ logger.debug(
283
+ `Sentinel: investigation (run ${aiRunId.toString()}) produced no analysis; nothing posted.`,
284
+ );
285
+ return;
286
+ }
287
+
288
+ const isConfident: boolean = !INCONCLUSIVE_RE.test(analysis);
289
+
290
+ await request.postAnalysis({
291
+ analysisMarkdown: this.buildBrandedMarkdown(result, analysis),
292
+ isConfident,
293
+ result,
294
+ });
295
+
296
+ logger.debug(
297
+ `Sentinel: investigation complete (run ${aiRunId.toString()}, confident=${isConfident}, ${result.llmCallCount} LLM calls, ${result.toolCallCount} tools, ${result.totalTokens} tokens).`,
298
+ );
299
+ } catch (error) {
300
+ const message: string =
301
+ error instanceof Error ? error.message : String(error);
302
+
303
+ await AIRunService.updateOneBy({
304
+ query: { _id: aiRunId.toString(), status: AIRunStatus.Running },
305
+ data: {
306
+ status: AIRunStatus.Error,
307
+ completedAt: OneUptimeDate.getCurrentDate(),
308
+ errorMessage: message.substring(0, 480),
309
+ } as never,
310
+ props: { isRoot: true },
311
+ });
312
+
313
+ await this.emitEvent({
314
+ projectId,
315
+ aiRunId,
316
+ sequence: sequence++,
317
+ eventType: AIRunEventType.RunFailed,
318
+ });
319
+
320
+ logger.error(
321
+ `Sentinel: investigation failed (run ${aiRunId.toString()}): ${message}`,
322
+ );
323
+ }
324
+ }
325
+
326
+ // Wrap the agent's analysis with Sentinel branding + an evidence list.
327
+ private static buildBrandedMarkdown(
328
+ result: ObservabilityAssistantResult,
329
+ analysisMarkdown: string,
330
+ ): string {
331
+ let markdown: string = `## 🧠 Sentinel — Automated Root Cause Analysis\n\n${analysisMarkdown}`;
332
+
333
+ const citations: Array<AIChatCitation> = result.citations || [];
334
+
335
+ if (citations.length > 0) {
336
+ markdown += `\n\n**Evidence checked**`;
337
+ for (const citation of citations.slice(0, 15)) {
338
+ markdown += `\n- **[${citation.id}]** ${citation.label} — ${citation.rowCount} row(s)`;
339
+ }
340
+ }
341
+
342
+ markdown += `\n\n---\n*Investigated automatically by OneUptime Sentinel — read-only, ${result.toolCallCount} quer${
343
+ result.toolCallCount === 1 ? "y" : "ies"
344
+ } run across your own telemetry${
345
+ result.modelName ? ` using ${result.modelName}` : ""
346
+ }. This is an AI-generated first pass; verify before acting.*`;
347
+
348
+ return markdown;
349
+ }
350
+
351
+ // Refresh lastHeartbeatAt so a long-running investigation isn't swept as stale.
352
+ private static async touchHeartbeat(aiRunId: ObjectID): Promise<void> {
353
+ try {
354
+ await AIRunService.updateOneBy({
355
+ query: { _id: aiRunId.toString(), status: AIRunStatus.Running },
356
+ data: {
357
+ lastHeartbeatAt: OneUptimeDate.getCurrentDate(),
358
+ } as never,
359
+ props: { isRoot: true },
360
+ });
361
+ } catch (error) {
362
+ logger.error(`Sentinel: heartbeat update failed: ${error}`);
363
+ }
364
+ }
365
+
366
+ private static async emitEvent(data: {
367
+ projectId: ObjectID;
368
+ aiRunId: ObjectID;
369
+ sequence: number;
370
+ eventType: AIRunEventType;
371
+ toolName?: string | undefined;
372
+ toolArguments?: JSONObject | undefined;
373
+ resultSummary?: AIRunEventResultSummary | undefined;
374
+ citationId?: string | undefined;
375
+ }): Promise<void> {
376
+ try {
377
+ const event: AIRunEvent = new AIRunEvent();
378
+ event.projectId = data.projectId;
379
+ event.aiRunId = data.aiRunId;
380
+ event.sequence = data.sequence;
381
+ event.eventType = data.eventType;
382
+
383
+ if (data.toolName) {
384
+ event.toolName = data.toolName;
385
+ }
386
+ if (data.toolArguments) {
387
+ event.toolArguments = data.toolArguments;
388
+ }
389
+ if (data.resultSummary) {
390
+ event.resultSummary = data.resultSummary;
391
+ }
392
+ if (data.citationId) {
393
+ event.citationId = data.citationId;
394
+ }
395
+
396
+ await AIRunEventService.create({
397
+ data: event,
398
+ props: { isRoot: true },
399
+ });
400
+ } catch (error) {
401
+ // Events are best-effort telemetry — never fail the run because of them.
402
+ logger.error(`Sentinel: failed to emit AIRunEvent: ${error}`);
403
+ }
404
+ }
405
+ }