@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.
- package/Models/DatabaseModels/AIRun.ts +50 -0
- package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
- package/Models/DatabaseModels/IncidentMember.ts +1 -1
- package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/MigrationFailure.ts +170 -0
- package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
- package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
- package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
- package/Models/DatabaseModels/Project.ts +60 -0
- package/Server/API/AIInvestigationAPI.ts +148 -0
- package/Server/API/AlertAPI.ts +1 -1
- package/Server/API/IncidentAPI.ts +2 -2
- package/Server/API/IncidentEpisodeAPI.ts +1 -1
- package/Server/API/LlmProviderAPI.ts +1 -0
- package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
- package/Server/Infrastructure/PostgresDatabase.ts +11 -0
- package/Server/Services/AIService.ts +74 -2
- package/Server/Services/AlertService.ts +25 -0
- package/Server/Services/AnalyticsDatabaseService.ts +10 -1
- package/Server/Services/DatabaseService.ts +10 -1
- package/Server/Services/IncidentService.ts +26 -1
- package/Server/Services/IncidentStateTimelineService.ts +19 -0
- package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
- package/Server/Services/IncomingCallPolicyService.ts +42 -1
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MigrationFailureService.ts +9 -0
- package/Server/Services/MonitorProbeService.ts +33 -7
- package/Server/Services/MonitorService.ts +19 -7
- package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
- package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
- package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
- package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
- package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
- package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
- package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
- package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
- package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
- package/Server/Services/TeamMemberService.ts +6 -0
- package/Server/Services/UserNotificationRuleService.ts +13 -42
- package/Server/Services/UserOnCallLogService.ts +90 -0
- package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
- package/Server/Types/Database/QueryUtil.ts +57 -0
- package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
- package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
- package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
- package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
- package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
- package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
- package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
- package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
- package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
- package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
- package/Server/Utils/AI/Toolbox/Index.ts +15 -1
- package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
- package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
- package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
- package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
- package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
- package/Server/Utils/LLM/LLMService.ts +110 -11
- package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
- package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
- package/Tests/Server/API/BaseAPI.test.ts +1 -0
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
- package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
- package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
- package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
- package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
- package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
- package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
- package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
- package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
- package/Tests/Types/Events/Recurring.test.ts +22 -9
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
- package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
- package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
- package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
- package/Types/Date.ts +246 -15
- package/Types/OnCallDutyPolicy/Layer.ts +790 -260
- package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
- package/UI/Components/Calendar/Calendar.tsx +8 -2
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
- package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
- package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
- package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
- package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +62 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
- package/build/dist/Server/API/AlertAPI.js +1 -1
- package/build/dist/Server/API/IncidentAPI.js +2 -2
- package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +1 -0
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +53 -2
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +24 -0
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +10 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +25 -1
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
- package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
- package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MigrationFailureService.js +9 -0
- package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
- package/build/dist/Server/Services/MonitorProbeService.js +33 -7
- package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +12 -5
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
- package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
- package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
- package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
- package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
- package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
- package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +6 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
- package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
- package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
- package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
- package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
- package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
- package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
- package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
- package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
- package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
- package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +10 -1
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
- package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
- package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
- package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
- package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
- package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
- package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
- package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
- package/build/dist/Types/Date.js +185 -15
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
- package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
- package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
- package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
- package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
- package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
- package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
- 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
|
+
}
|