@oneuptime/common 11.3.28 → 11.4.1
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/Project.ts +60 -0
- package/Server/API/AIAgentTaskAPI.ts +10 -24
- package/Server/API/AIInvestigationAPI.ts +148 -0
- package/Server/API/TelemetryAPI.ts +4 -3
- 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 +6 -0
- package/Server/Services/AIAgentTaskService.ts +62 -0
- package/Server/Services/AlertService.ts +25 -0
- package/Server/Services/AnalyticsDatabaseService.ts +27 -0
- package/Server/Services/IncidentService.ts +25 -0
- package/Server/Services/IncidentStateTimelineService.ts +19 -0
- package/Server/Services/LogAggregationService.ts +29 -11
- package/Server/Services/MetricService.ts +603 -13
- package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
- 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/README.md +10 -0
- package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
- package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
- package/Server/Utils/AI/Toolbox/Index.ts +11 -0
- package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +44 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +108 -2
- package/Tests/Server/API/BaseAPI.test.ts +1 -0
- package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +146 -0
- package/Tests/Server/Services/LogAggregationService.test.ts +22 -0
- package/Tests/Server/Services/MetricServiceAggregate.test.ts +194 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +86 -0
- package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +127 -0
- package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.test.ts +152 -0
- package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
- package/Tests/Types/Trace/TraceRecordingRuleDefinition.test.ts +308 -0
- package/Tests/UI/Components/Dropdown.test.tsx +17 -0
- package/Tests/UI/Components/EntityDropdown.test.tsx +130 -0
- package/Tests/UI/Components/Modal.test.tsx +144 -2
- package/Tests/UI/Components/TelemetrySearchBar.test.tsx +90 -0
- package/Tests/UI/Utils/Theme.test.ts +161 -0
- package/Types/BaseDatabase/AggregateBy.ts +12 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +19 -0
- package/Types/Dashboard/DashboardTemplates.ts +9 -1
- package/Types/Date.ts +46 -0
- package/Types/Monitor/CriteriaFilter.ts +4 -0
- package/Types/Monitor/HttpPhaseTimings.ts +16 -0
- package/Types/Monitor/MonitorMetricType.ts +17 -0
- package/Types/Monitor/PingMonitor/PingMonitorResponse.ts +20 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
- package/Types/Probe/ProbeMonitorResponse.ts +14 -0
- package/UI/Components/Calendar/Calendar.tsx +8 -2
- package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -5
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +4 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +6 -3
- package/UI/Components/CodeBlock/CodeBlock.tsx +2 -1
- package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
- package/UI/Components/Divider/Divider.tsx +1 -1
- package/UI/Components/Dropdown/Dropdown.tsx +13 -4
- package/UI/Components/EntityDropdown/EntityDropdown.tsx +110 -2
- package/UI/Components/ErrorBoundary.tsx +2 -1
- package/UI/Components/EventItem/EventItem.tsx +2 -2
- package/UI/Components/GanttChart/Bar/Index.tsx +1 -1
- package/UI/Components/Graphs/UptimeBarTooltip.tsx +30 -23
- package/UI/Components/Header/Header.tsx +7 -0
- package/UI/Components/Header/HeaderIconDropdownButton.tsx +2 -0
- package/UI/Components/Label/Label.tsx +2 -2
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +22 -10
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +9 -3
- package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +8 -8
- package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +44 -20
- package/UI/Components/Modal/ConfirmModal.tsx +1 -1
- package/UI/Components/Modal/Modal.tsx +213 -121
- package/UI/Components/Modal/ModalFooter.tsx +33 -22
- package/UI/Components/ModelTable/TableView.tsx +1 -1
- package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +20 -12
- package/UI/Components/Pill/Pill.tsx +21 -13
- package/UI/Components/StatusBubble/StatusBubble.tsx +3 -3
- package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -1
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +9 -3
- package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +62 -4
- package/UI/Components/Workflow/Component.tsx +53 -33
- package/UI/Components/Workflow/ComponentPortViewer.tsx +6 -6
- package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +8 -8
- package/UI/Components/Workflow/ComponentsModal.tsx +16 -9
- package/UI/Components/Workflow/Workflow.tsx +13 -11
- package/UI/Utils/Theme.ts +140 -0
- package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +104 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/Utils/Monitor/MonitorMetricType.ts +70 -2
- package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
- package/build/dist/Models/DatabaseModels/AIRun.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/AIAgentTaskAPI.js +8 -23
- package/build/dist/Server/API/AIAgentTaskAPI.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/TelemetryAPI.js.map +1 -1
- 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 +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIAgentTaskService.js +59 -0
- package/build/dist/Server/Services/AIAgentTaskService.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 +19 -0
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +24 -0
- 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/LogAggregationService.js +21 -7
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +449 -7
- package/build/dist/Server/Services/MetricService.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/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/Index.js +6 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- 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/Monitor/Criteria/APIRequestCriteria.js +29 -0
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +86 -2
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +9 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Date.js +32 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/Monitor/HttpPhaseTimings.js +2 -0
- package/build/dist/Types/Monitor/HttpPhaseTimings.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js +15 -0
- package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js +2 -0
- package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
- 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/Charts/ChartLibrary/AreaChart/AreaChart.js +7 -4
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +4 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +6 -3
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js +1 -1
- package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js +3 -1
- package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
- package/build/dist/UI/Components/Divider/Divider.js +1 -1
- package/build/dist/UI/Components/Divider/Divider.js.map +1 -1
- package/build/dist/UI/Components/Dropdown/Dropdown.js +10 -4
- package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +76 -3
- package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
- package/build/dist/UI/Components/ErrorBoundary.js +2 -1
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
- package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js +1 -1
- package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +29 -23
- package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
- package/build/dist/UI/Components/Header/Header.js +1 -0
- package/build/dist/UI/Components/Header/Header.js.map +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
- package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
- package/build/dist/UI/Components/Label/Label.js +2 -2
- package/build/dist/UI/Components/Label/Label.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +20 -8
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +8 -2
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +8 -8
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +40 -20
- package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +109 -51
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +13 -10
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +17 -12
- package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
- package/build/dist/UI/Components/Pill/Pill.js +12 -13
- package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js +3 -3
- package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
- package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -2
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +36 -6
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Component.js +43 -33
- package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js +6 -6
- package/build/dist/UI/Components/Workflow/ComponentPortViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +8 -8
- package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ComponentsModal.js +15 -9
- package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
- package/build/dist/UI/Components/Workflow/Workflow.js +13 -11
- package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
- package/build/dist/UI/Utils/Theme.js +106 -0
- package/build/dist/UI/Utils/Theme.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +92 -0
- package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +70 -3
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import DatabaseCommonInteractionProps from "../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
2
2
|
import OneUptimeDate from "../../../../Types/Date";
|
|
3
3
|
import ObjectID from "../../../../Types/ObjectID";
|
|
4
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
4
5
|
import { AIChatCitation } from "../../../../Types/AI/AIChatTypes";
|
|
5
6
|
import AIService, { AILogResponse } from "../../../Services/AIService";
|
|
6
7
|
import logger from "../../Logger";
|
|
@@ -32,6 +33,29 @@ export interface ObservabilityAssistantPriorTurn {
|
|
|
32
33
|
content: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
/*
|
|
37
|
+
* A single "thinking step" emitted live as the agent loop runs, so callers can
|
|
38
|
+
* narrate the investigation in real time ("Reading trace ✓ 0.4s"). This is the
|
|
39
|
+
* step-level narration the OneUptime UI renders by polling AIRunEvent rows.
|
|
40
|
+
*/
|
|
41
|
+
export type ObservabilityAssistantStepType =
|
|
42
|
+
| "llm_started"
|
|
43
|
+
| "llm_completed"
|
|
44
|
+
| "tool_started"
|
|
45
|
+
| "tool_completed"
|
|
46
|
+
| "tool_failed";
|
|
47
|
+
|
|
48
|
+
export interface ObservabilityAssistantStep {
|
|
49
|
+
type: ObservabilityAssistantStepType;
|
|
50
|
+
toolName?: string | undefined;
|
|
51
|
+
toolArguments?: JSONObject | undefined;
|
|
52
|
+
rowCount?: number | undefined;
|
|
53
|
+
durationMs?: number | undefined;
|
|
54
|
+
citationId?: string | undefined;
|
|
55
|
+
totalTokens?: number | undefined;
|
|
56
|
+
errorMessage?: string | undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
35
59
|
export interface ObservabilityAssistantRequest {
|
|
36
60
|
projectId: ObjectID;
|
|
37
61
|
userId?: ObjectID | undefined;
|
|
@@ -44,6 +68,29 @@ export interface ObservabilityAssistantRequest {
|
|
|
44
68
|
llmProviderId?: ObjectID | undefined;
|
|
45
69
|
// Label recorded on LlmLog, e.g. "Slack ChatOps".
|
|
46
70
|
feature: string;
|
|
71
|
+
/*
|
|
72
|
+
* Extra instructions appended to the base observability system prompt — used
|
|
73
|
+
* to give an autonomous run (e.g. Sentinel incident investigation) a distinct
|
|
74
|
+
* persona and task framing while keeping the same hard rules (citations, no
|
|
75
|
+
* fabrication, read-only). The base prompt's grounding rules always win.
|
|
76
|
+
*/
|
|
77
|
+
systemInstructions?: string | undefined;
|
|
78
|
+
/*
|
|
79
|
+
* Optional budget overrides. Autonomous investigations get more room than an
|
|
80
|
+
* interactive chat-ops answer, which must return in a single quick message.
|
|
81
|
+
* Omitted values fall back to the interactive defaults.
|
|
82
|
+
*/
|
|
83
|
+
maxLlmCalls?: number | undefined;
|
|
84
|
+
maxToolCalls?: number | undefined;
|
|
85
|
+
maxWallClockMs?: number | undefined;
|
|
86
|
+
maxOutputTokens?: number | undefined;
|
|
87
|
+
/*
|
|
88
|
+
* Optional live-narration hook, fired for each LLM/tool step as the loop runs.
|
|
89
|
+
* Used by autonomous runs to persist a per-step AIRunEvent trail so the UI can
|
|
90
|
+
* "watch it think". Best-effort — implementations must not throw; a slow or
|
|
91
|
+
* failing handler should not break the run.
|
|
92
|
+
*/
|
|
93
|
+
onStep?: ((step: ObservabilityAssistantStep) => Promise<void>) | undefined;
|
|
47
94
|
}
|
|
48
95
|
|
|
49
96
|
export interface ObservabilityAssistantResult {
|
|
@@ -70,19 +117,50 @@ export default class ObservabilityAssistant {
|
|
|
70
117
|
): Promise<ObservabilityAssistantResult> {
|
|
71
118
|
const startedAtMs: number = Date.now();
|
|
72
119
|
|
|
120
|
+
const maxLlmCalls: number = request.maxLlmCalls ?? MAX_LLM_CALLS;
|
|
121
|
+
const maxToolCalls: number = request.maxToolCalls ?? MAX_TOOL_CALLS;
|
|
122
|
+
const maxWallClockMs: number = request.maxWallClockMs ?? MAX_WALL_CLOCK_MS;
|
|
123
|
+
const maxOutputTokens: number =
|
|
124
|
+
request.maxOutputTokens ?? MAX_OUTPUT_TOKENS;
|
|
125
|
+
|
|
126
|
+
// Best-effort live-narration emitter — never throws into the loop.
|
|
127
|
+
const emitStep: (
|
|
128
|
+
step: ObservabilityAssistantStep,
|
|
129
|
+
) => Promise<void> = async (
|
|
130
|
+
step: ObservabilityAssistantStep,
|
|
131
|
+
): Promise<void> => {
|
|
132
|
+
if (!request.onStep) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await request.onStep(step);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
logger.error(`ObservabilityAssistant onStep failed: ${err}`);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
73
142
|
const toolContext: ToolContext = {
|
|
74
143
|
projectId: request.projectId,
|
|
75
144
|
props: request.props,
|
|
76
145
|
};
|
|
77
146
|
|
|
147
|
+
let systemPromptContent: string = buildObservabilityChatSystemPrompt({
|
|
148
|
+
currentTime: OneUptimeDate.getCurrentDate(),
|
|
149
|
+
/*
|
|
150
|
+
* Slack/Teams and autonomous investigations have no approval UI, so this
|
|
151
|
+
* surface stays strictly read-only.
|
|
152
|
+
*/
|
|
153
|
+
permissionMode: AIChatPermissionMode.ReadOnly,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (request.systemInstructions) {
|
|
157
|
+
systemPromptContent += `\n\n${request.systemInstructions}`;
|
|
158
|
+
}
|
|
159
|
+
|
|
78
160
|
const messages: Array<LLMMessage> = [
|
|
79
161
|
{
|
|
80
162
|
role: "system",
|
|
81
|
-
content:
|
|
82
|
-
currentTime: OneUptimeDate.getCurrentDate(),
|
|
83
|
-
// Slack/Teams have no approval UI, so this surface stays read-only.
|
|
84
|
-
permissionMode: AIChatPermissionMode.ReadOnly,
|
|
85
|
-
}),
|
|
163
|
+
content: systemPromptContent,
|
|
86
164
|
},
|
|
87
165
|
];
|
|
88
166
|
|
|
@@ -104,9 +182,9 @@ export default class ObservabilityAssistant {
|
|
|
104
182
|
|
|
105
183
|
while (true) {
|
|
106
184
|
const budgetExhausted: boolean =
|
|
107
|
-
llmCallCount >=
|
|
108
|
-
toolCallCount >=
|
|
109
|
-
Date.now() - startedAtMs >=
|
|
185
|
+
llmCallCount >= maxLlmCalls - 1 ||
|
|
186
|
+
toolCallCount >= maxToolCalls ||
|
|
187
|
+
Date.now() - startedAtMs >= maxWallClockMs;
|
|
110
188
|
|
|
111
189
|
if (budgetExhausted) {
|
|
112
190
|
messages.push({
|
|
@@ -116,6 +194,8 @@ export default class ObservabilityAssistant {
|
|
|
116
194
|
});
|
|
117
195
|
}
|
|
118
196
|
|
|
197
|
+
await emitStep({ type: "llm_started" });
|
|
198
|
+
|
|
119
199
|
const response: AILogResponse = await AIService.executeWithLogging({
|
|
120
200
|
projectId: request.projectId,
|
|
121
201
|
userId: request.userId,
|
|
@@ -125,7 +205,7 @@ export default class ObservabilityAssistant {
|
|
|
125
205
|
tools: budgetExhausted
|
|
126
206
|
? undefined
|
|
127
207
|
: AIToolbox.getLlmToolDefinitions(AIChatPermissionMode.ReadOnly),
|
|
128
|
-
maxTokens:
|
|
208
|
+
maxTokens: maxOutputTokens,
|
|
129
209
|
temperature: TEMPERATURE,
|
|
130
210
|
// Chat-ops content is per-user — do not persist previews to LlmLog.
|
|
131
211
|
storeContentPreviews: false,
|
|
@@ -134,6 +214,11 @@ export default class ObservabilityAssistant {
|
|
|
134
214
|
llmCallCount++;
|
|
135
215
|
totalTokens += response.llmLog.totalTokens || 0;
|
|
136
216
|
|
|
217
|
+
await emitStep({
|
|
218
|
+
type: "llm_completed",
|
|
219
|
+
totalTokens: response.llmLog.totalTokens || 0,
|
|
220
|
+
});
|
|
221
|
+
|
|
137
222
|
if (!providerName) {
|
|
138
223
|
providerName = response.llmLog.llmProviderName;
|
|
139
224
|
modelName = response.llmLog.modelName;
|
|
@@ -152,8 +237,8 @@ export default class ObservabilityAssistant {
|
|
|
152
237
|
|
|
153
238
|
for (const toolCall of response.toolCalls) {
|
|
154
239
|
const overBudget: boolean =
|
|
155
|
-
toolCallCount >=
|
|
156
|
-
Date.now() - startedAtMs >=
|
|
240
|
+
toolCallCount >= maxToolCalls ||
|
|
241
|
+
Date.now() - startedAtMs >= maxWallClockMs;
|
|
157
242
|
|
|
158
243
|
if (overBudget) {
|
|
159
244
|
messages.push({
|
|
@@ -176,6 +261,14 @@ export default class ObservabilityAssistant {
|
|
|
176
261
|
continue;
|
|
177
262
|
}
|
|
178
263
|
|
|
264
|
+
const toolStartedAtMs: number = Date.now();
|
|
265
|
+
|
|
266
|
+
await emitStep({
|
|
267
|
+
type: "tool_started",
|
|
268
|
+
toolName: toolCall.name,
|
|
269
|
+
toolArguments: toolCall.arguments,
|
|
270
|
+
});
|
|
271
|
+
|
|
179
272
|
const outcome: ToolCallOutcome = await AIToolbox.executeTool({
|
|
180
273
|
name: toolCall.name,
|
|
181
274
|
args: toolCall.arguments,
|
|
@@ -183,6 +276,13 @@ export default class ObservabilityAssistant {
|
|
|
183
276
|
});
|
|
184
277
|
|
|
185
278
|
if (!outcome.success || !outcome.result) {
|
|
279
|
+
await emitStep({
|
|
280
|
+
type: "tool_failed",
|
|
281
|
+
toolName: toolCall.name,
|
|
282
|
+
durationMs: Date.now() - toolStartedAtMs,
|
|
283
|
+
errorMessage: outcome.errorMessage || outcome.textForLlm,
|
|
284
|
+
});
|
|
285
|
+
|
|
186
286
|
messages.push({
|
|
187
287
|
role: "tool",
|
|
188
288
|
toolCallId: toolCall.id,
|
|
@@ -202,6 +302,14 @@ export default class ObservabilityAssistant {
|
|
|
202
302
|
target: outcome.result.citationTarget,
|
|
203
303
|
});
|
|
204
304
|
|
|
305
|
+
await emitStep({
|
|
306
|
+
type: "tool_completed",
|
|
307
|
+
toolName: toolCall.name,
|
|
308
|
+
durationMs: Date.now() - toolStartedAtMs,
|
|
309
|
+
rowCount: outcome.result.rowCount,
|
|
310
|
+
citationId: citationId,
|
|
311
|
+
});
|
|
312
|
+
|
|
205
313
|
const escapedText: string = escapeToolResultContent(
|
|
206
314
|
outcome.textForLlm,
|
|
207
315
|
);
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
2
|
+
import OneUptimeDate from "../../../../Types/Date";
|
|
3
|
+
import { Blue500 } from "../../../../Types/BrandColors";
|
|
4
|
+
import { AlertFeedEventType } from "../../../../Models/DatabaseModels/AlertFeed";
|
|
5
|
+
import AlertFeedService from "../../../Services/AlertFeedService";
|
|
6
|
+
import AlertAIContextBuilder, {
|
|
7
|
+
AlertContextData,
|
|
8
|
+
} from "../AlertAIContextBuilder";
|
|
9
|
+
import SentinelInvestigationEngine from "./SentinelInvestigationEngine";
|
|
10
|
+
import { ObservabilityAssistantResult } from "../Chat/ObservabilityAssistant";
|
|
11
|
+
import logger from "../../Logger";
|
|
12
|
+
import CaptureSpan from "../../Telemetry/CaptureSpan";
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Sentinel — alert investigation.
|
|
16
|
+
*
|
|
17
|
+
* Alerts are the earlier, "left of boom" signal. When a new alert is declared,
|
|
18
|
+
* Sentinel investigates it (read-only) and posts a cited root-cause analysis to
|
|
19
|
+
* the alert timeline + Slack/Teams. Symmetric to incident investigation and
|
|
20
|
+
* built on the same shared SentinelInvestigationEngine.
|
|
21
|
+
*
|
|
22
|
+
* Gated by the same per-project opt-in as incidents. Alert volume can be higher
|
|
23
|
+
* than incidents, so severity / rate gating is a deliberate follow-up.
|
|
24
|
+
*/
|
|
25
|
+
export default class SentinelAlertInvestigationRunner {
|
|
26
|
+
/*
|
|
27
|
+
* Entry point called (fire-and-forget) from AlertService.onCreateSuccess.
|
|
28
|
+
* Never throws — all failures are logged and recorded on the AIRun.
|
|
29
|
+
*/
|
|
30
|
+
@CaptureSpan()
|
|
31
|
+
public static async investigateNewAlert(data: {
|
|
32
|
+
alertId: ObjectID;
|
|
33
|
+
projectId: ObjectID;
|
|
34
|
+
}): Promise<void> {
|
|
35
|
+
const { alertId, projectId } = data;
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (
|
|
39
|
+
!(await SentinelInvestigationEngine.isEnabledForProject(
|
|
40
|
+
projectId,
|
|
41
|
+
"Alert",
|
|
42
|
+
))
|
|
43
|
+
) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const contextData: AlertContextData =
|
|
48
|
+
await AlertAIContextBuilder.buildAlertContext({
|
|
49
|
+
alertId,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await SentinelInvestigationEngine.investigate({
|
|
53
|
+
projectId,
|
|
54
|
+
feature: "Sentinel Alert Investigation",
|
|
55
|
+
subjectAlertId: alertId,
|
|
56
|
+
contextSummary: this.buildAlertSummary(contextData),
|
|
57
|
+
postAnalysis: async (postData: {
|
|
58
|
+
analysisMarkdown: string;
|
|
59
|
+
isConfident: boolean;
|
|
60
|
+
result: ObservabilityAssistantResult;
|
|
61
|
+
}): Promise<void> => {
|
|
62
|
+
await AlertFeedService.createAlertFeedItem({
|
|
63
|
+
alertId,
|
|
64
|
+
projectId,
|
|
65
|
+
alertFeedEventType: AlertFeedEventType.RootCause,
|
|
66
|
+
displayColor: Blue500,
|
|
67
|
+
feedInfoInMarkdown: postData.analysisMarkdown,
|
|
68
|
+
/*
|
|
69
|
+
* Quiet mode: an inconclusive investigation posts to the timeline
|
|
70
|
+
* but does NOT loudly ping the workspace / on-call.
|
|
71
|
+
*/
|
|
72
|
+
workspaceNotification: {
|
|
73
|
+
sendWorkspaceNotification: postData.isConfident,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(
|
|
80
|
+
`Sentinel: unexpected error investigating alert ${alertId.toString()}: ${error}`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Build a compact alert record to seed the investigation.
|
|
86
|
+
private static buildAlertSummary(contextData: AlertContextData): string {
|
|
87
|
+
const { alert, internalNotes } = contextData;
|
|
88
|
+
|
|
89
|
+
const lines: Array<string> = [];
|
|
90
|
+
lines.push("# Alert");
|
|
91
|
+
lines.push(`Title: ${alert.title || "N/A"}`);
|
|
92
|
+
|
|
93
|
+
if (alert.description) {
|
|
94
|
+
lines.push(`Description: ${alert.description}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
lines.push(`Severity: ${alert.alertSeverity?.name || "N/A"}`);
|
|
98
|
+
lines.push(`Current state: ${alert.currentAlertState?.name || "N/A"}`);
|
|
99
|
+
lines.push(
|
|
100
|
+
`Declared at: ${
|
|
101
|
+
alert.createdAt
|
|
102
|
+
? OneUptimeDate.getDateAsFormattedString(alert.createdAt)
|
|
103
|
+
: "N/A"
|
|
104
|
+
}`,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (alert.monitor?.name) {
|
|
108
|
+
lines.push(`Affected monitor: ${alert.monitor.name}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (alert.labels && alert.labels.length > 0) {
|
|
112
|
+
lines.push(
|
|
113
|
+
`Labels: ${alert.labels
|
|
114
|
+
.map((l: { name?: string }) => {
|
|
115
|
+
return l.name;
|
|
116
|
+
})
|
|
117
|
+
.filter(Boolean)
|
|
118
|
+
.join(", ")}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (alert.rootCause) {
|
|
123
|
+
lines.push(`Root cause (as recorded so far): ${alert.rootCause}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const recentNotes: Array<string> = (internalNotes || [])
|
|
127
|
+
.slice(-5)
|
|
128
|
+
.map((note: { note?: string }) => {
|
|
129
|
+
return note.note ? `- ${note.note}` : "";
|
|
130
|
+
})
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
|
|
133
|
+
if (recentNotes.length > 0) {
|
|
134
|
+
lines.push("");
|
|
135
|
+
lines.push("Recent internal notes:");
|
|
136
|
+
lines.push(...recentNotes);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return lines.join("\n");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -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/AISentinelExecution.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,10 @@
|
|
|
1
|
+
# Sentinel — autonomous investigation engine
|
|
2
|
+
|
|
3
|
+
Wake-on-signal AI SRE: when an incident or alert is created (and the project has opted in), Sentinel runs a read-only, budgeted, citation-minting investigation over the project's own telemetry and posts a root-cause analysis to the subject's feed, with a glass-box `AIRun`/`AIRunEvent` trail behind a live dashboard panel.
|
|
4
|
+
|
|
5
|
+
- `SentinelInvestigationEngine.ts` — shared run lifecycle, budgets, persona, quiet-mode confidence gate
|
|
6
|
+
- `IncidentInvestigationRunner.ts` / `AlertInvestigationRunner.ts` — per-subject context assembly + result posting
|
|
7
|
+
- `IncidentPostmortemRunner.ts` — auto-draft postmortem when an incident resolves
|
|
8
|
+
- `SentinelMemory.ts` — recurrence context from past resolved incidents (retrieval-only v1)
|
|
9
|
+
|
|
10
|
+
**Roadmap & status:** [Internal/Roadmap/AISentinelExecution.md](../../../../../Internal/Roadmap/AISentinelExecution.md) (tracker — update it in the same PR when you change behavior here) and [Internal/Roadmap/AISentinelVision.md](../../../../../Internal/Roadmap/AISentinelVision.md) (strategy).
|