@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.
Files changed (244) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/Project.ts +60 -0
  3. package/Server/API/AIAgentTaskAPI.ts +10 -24
  4. package/Server/API/AIInvestigationAPI.ts +148 -0
  5. package/Server/API/TelemetryAPI.ts +4 -3
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  10. package/Server/Services/AIAgentTaskService.ts +62 -0
  11. package/Server/Services/AlertService.ts +25 -0
  12. package/Server/Services/AnalyticsDatabaseService.ts +27 -0
  13. package/Server/Services/IncidentService.ts +25 -0
  14. package/Server/Services/IncidentStateTimelineService.ts +19 -0
  15. package/Server/Services/LogAggregationService.ts +29 -11
  16. package/Server/Services/MetricService.ts +603 -13
  17. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
  18. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
  19. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
  20. package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
  21. package/Server/Utils/AI/Sentinel/README.md +10 -0
  22. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
  23. package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
  24. package/Server/Utils/AI/Toolbox/Index.ts +11 -0
  25. package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
  26. package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +44 -0
  27. package/Server/Utils/Monitor/MonitorMetricUtil.ts +108 -2
  28. package/Tests/Server/API/BaseAPI.test.ts +1 -0
  29. package/Tests/Server/Services/AIAgentTaskServiceClaim.test.ts +146 -0
  30. package/Tests/Server/Services/LogAggregationService.test.ts +22 -0
  31. package/Tests/Server/Services/MetricServiceAggregate.test.ts +194 -0
  32. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +86 -0
  33. package/Tests/Types/BaseDatabase/AggregationIntervalUtil.test.ts +127 -0
  34. package/Tests/Types/BaseDatabase/DatabaseCommonInteractionPropsUtil.test.ts +152 -0
  35. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
  36. package/Tests/Types/Trace/TraceRecordingRuleDefinition.test.ts +308 -0
  37. package/Tests/UI/Components/Dropdown.test.tsx +17 -0
  38. package/Tests/UI/Components/EntityDropdown.test.tsx +130 -0
  39. package/Tests/UI/Components/Modal.test.tsx +144 -2
  40. package/Tests/UI/Components/TelemetrySearchBar.test.tsx +90 -0
  41. package/Tests/UI/Utils/Theme.test.ts +161 -0
  42. package/Types/BaseDatabase/AggregateBy.ts +12 -0
  43. package/Types/Dashboard/DashboardComponentType.ts +1 -0
  44. package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
  45. package/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.ts +19 -0
  46. package/Types/Dashboard/DashboardTemplates.ts +9 -1
  47. package/Types/Date.ts +46 -0
  48. package/Types/Monitor/CriteriaFilter.ts +4 -0
  49. package/Types/Monitor/HttpPhaseTimings.ts +16 -0
  50. package/Types/Monitor/MonitorMetricType.ts +17 -0
  51. package/Types/Monitor/PingMonitor/PingMonitorResponse.ts +20 -0
  52. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
  53. package/Types/Probe/ProbeMonitorResponse.ts +14 -0
  54. package/UI/Components/Calendar/Calendar.tsx +8 -2
  55. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  56. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -5
  57. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +4 -1
  58. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +6 -3
  59. package/UI/Components/CodeBlock/CodeBlock.tsx +2 -1
  60. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  61. package/UI/Components/Divider/Divider.tsx +1 -1
  62. package/UI/Components/Dropdown/Dropdown.tsx +13 -4
  63. package/UI/Components/EntityDropdown/EntityDropdown.tsx +110 -2
  64. package/UI/Components/ErrorBoundary.tsx +2 -1
  65. package/UI/Components/EventItem/EventItem.tsx +2 -2
  66. package/UI/Components/GanttChart/Bar/Index.tsx +1 -1
  67. package/UI/Components/Graphs/UptimeBarTooltip.tsx +30 -23
  68. package/UI/Components/Header/Header.tsx +7 -0
  69. package/UI/Components/Header/HeaderIconDropdownButton.tsx +2 -0
  70. package/UI/Components/Label/Label.tsx +2 -2
  71. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +22 -10
  72. package/UI/Components/LogsViewer/components/LogsHistogram.tsx +9 -3
  73. package/UI/Components/Markdown.tsx/MarkdownEditor.tsx +8 -8
  74. package/UI/Components/Markdown.tsx/MarkdownViewer.tsx +44 -20
  75. package/UI/Components/Modal/ConfirmModal.tsx +1 -1
  76. package/UI/Components/Modal/Modal.tsx +213 -121
  77. package/UI/Components/Modal/ModalFooter.tsx +33 -22
  78. package/UI/Components/ModelTable/TableView.tsx +1 -1
  79. package/UI/Components/MonitorGraphs/UptimeBarDayModal.tsx +20 -12
  80. package/UI/Components/Pill/Pill.tsx +21 -13
  81. package/UI/Components/StatusBubble/StatusBubble.tsx +3 -3
  82. package/UI/Components/TelemetryViewer/TelemetryViewer.tsx +7 -1
  83. package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +9 -3
  84. package/UI/Components/TelemetryViewer/components/TelemetrySearchBar.tsx +62 -4
  85. package/UI/Components/Workflow/Component.tsx +53 -33
  86. package/UI/Components/Workflow/ComponentPortViewer.tsx +6 -6
  87. package/UI/Components/Workflow/ComponentReturnValueViewer.tsx +8 -8
  88. package/UI/Components/Workflow/ComponentsModal.tsx +16 -9
  89. package/UI/Components/Workflow/Workflow.tsx +13 -11
  90. package/UI/Utils/Theme.ts +140 -0
  91. package/Utils/Dashboard/Components/DashboardLogChartComponent.ts +104 -0
  92. package/Utils/Dashboard/Components/Index.ts +7 -0
  93. package/Utils/Monitor/MonitorMetricType.ts +70 -2
  94. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  95. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  97. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  98. package/build/dist/Server/API/AIAgentTaskAPI.js +8 -23
  99. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  100. package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
  101. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
  102. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  103. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
  104. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
  105. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  111. package/build/dist/Server/Services/AIAgentTaskService.js +59 -0
  112. package/build/dist/Server/Services/AIAgentTaskService.js.map +1 -1
  113. package/build/dist/Server/Services/AlertService.js +24 -0
  114. package/build/dist/Server/Services/AlertService.js.map +1 -1
  115. package/build/dist/Server/Services/AnalyticsDatabaseService.js +19 -0
  116. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  117. package/build/dist/Server/Services/IncidentService.js +24 -0
  118. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  119. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  120. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  121. package/build/dist/Server/Services/LogAggregationService.js +21 -7
  122. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  123. package/build/dist/Server/Services/MetricService.js +449 -7
  124. package/build/dist/Server/Services/MetricService.js.map +1 -1
  125. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
  126. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  127. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
  128. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
  129. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
  130. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
  131. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
  132. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
  133. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
  134. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
  135. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
  136. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
  137. package/build/dist/Server/Utils/AI/Toolbox/Index.js +6 -0
  138. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  139. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
  140. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
  141. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +29 -0
  142. package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
  143. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +86 -2
  144. package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
  145. package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
  146. package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
  147. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
  148. package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
  149. package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js +2 -0
  150. package/build/dist/Types/Dashboard/DashboardComponents/DashboardLogChartComponent.js.map +1 -0
  151. package/build/dist/Types/Dashboard/DashboardTemplates.js +9 -1
  152. package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
  153. package/build/dist/Types/Date.js +32 -0
  154. package/build/dist/Types/Date.js.map +1 -1
  155. package/build/dist/Types/Monitor/CriteriaFilter.js +4 -0
  156. package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
  157. package/build/dist/Types/Monitor/HttpPhaseTimings.js +2 -0
  158. package/build/dist/Types/Monitor/HttpPhaseTimings.js.map +1 -0
  159. package/build/dist/Types/Monitor/MonitorMetricType.js +15 -0
  160. package/build/dist/Types/Monitor/MonitorMetricType.js.map +1 -1
  161. package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js +2 -0
  162. package/build/dist/Types/Monitor/PingMonitor/PingMonitorResponse.js.map +1 -0
  163. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
  164. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
  165. package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
  166. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  167. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  168. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  169. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +7 -4
  170. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  171. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +4 -1
  172. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  173. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +6 -3
  174. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  175. package/build/dist/UI/Components/CodeBlock/CodeBlock.js +1 -1
  176. package/build/dist/UI/Components/CodeBlock/CodeBlock.js.map +1 -1
  177. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +3 -1
  178. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  179. package/build/dist/UI/Components/Divider/Divider.js +1 -1
  180. package/build/dist/UI/Components/Divider/Divider.js.map +1 -1
  181. package/build/dist/UI/Components/Dropdown/Dropdown.js +10 -4
  182. package/build/dist/UI/Components/Dropdown/Dropdown.js.map +1 -1
  183. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +76 -3
  184. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  185. package/build/dist/UI/Components/ErrorBoundary.js +2 -1
  186. package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
  187. package/build/dist/UI/Components/EventItem/EventItem.js +2 -2
  188. package/build/dist/UI/Components/EventItem/EventItem.js.map +1 -1
  189. package/build/dist/UI/Components/GanttChart/Bar/Index.js +1 -1
  190. package/build/dist/UI/Components/GanttChart/Bar/Index.js.map +1 -1
  191. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js +29 -23
  192. package/build/dist/UI/Components/Graphs/UptimeBarTooltip.js.map +1 -1
  193. package/build/dist/UI/Components/Header/Header.js +1 -0
  194. package/build/dist/UI/Components/Header/Header.js.map +1 -1
  195. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js +1 -1
  196. package/build/dist/UI/Components/Header/HeaderIconDropdownButton.js.map +1 -1
  197. package/build/dist/UI/Components/Label/Label.js +2 -2
  198. package/build/dist/UI/Components/Label/Label.js.map +1 -1
  199. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +20 -8
  200. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  201. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +8 -2
  202. package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
  203. package/build/dist/UI/Components/Markdown.tsx/MarkdownEditor.js +8 -8
  204. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js +40 -20
  205. package/build/dist/UI/Components/Markdown.tsx/MarkdownViewer.js.map +1 -1
  206. package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
  207. package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
  208. package/build/dist/UI/Components/Modal/Modal.js +109 -51
  209. package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
  210. package/build/dist/UI/Components/Modal/ModalFooter.js +13 -10
  211. package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
  212. package/build/dist/UI/Components/ModelTable/TableView.js +1 -1
  213. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  214. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js +17 -12
  215. package/build/dist/UI/Components/MonitorGraphs/UptimeBarDayModal.js.map +1 -1
  216. package/build/dist/UI/Components/Pill/Pill.js +12 -13
  217. package/build/dist/UI/Components/Pill/Pill.js.map +1 -1
  218. package/build/dist/UI/Components/StatusBubble/StatusBubble.js +3 -3
  219. package/build/dist/UI/Components/StatusBubble/StatusBubble.js.map +1 -1
  220. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js +1 -1
  221. package/build/dist/UI/Components/TelemetryViewer/TelemetryViewer.js.map +1 -1
  222. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +8 -2
  223. package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
  224. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js +36 -6
  225. package/build/dist/UI/Components/TelemetryViewer/components/TelemetrySearchBar.js.map +1 -1
  226. package/build/dist/UI/Components/Workflow/Component.js +43 -33
  227. package/build/dist/UI/Components/Workflow/Component.js.map +1 -1
  228. package/build/dist/UI/Components/Workflow/ComponentPortViewer.js +6 -6
  229. package/build/dist/UI/Components/Workflow/ComponentPortViewer.js.map +1 -1
  230. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js +8 -8
  231. package/build/dist/UI/Components/Workflow/ComponentReturnValueViewer.js.map +1 -1
  232. package/build/dist/UI/Components/Workflow/ComponentsModal.js +15 -9
  233. package/build/dist/UI/Components/Workflow/ComponentsModal.js.map +1 -1
  234. package/build/dist/UI/Components/Workflow/Workflow.js +13 -11
  235. package/build/dist/UI/Components/Workflow/Workflow.js.map +1 -1
  236. package/build/dist/UI/Utils/Theme.js +106 -0
  237. package/build/dist/UI/Utils/Theme.js.map +1 -0
  238. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js +92 -0
  239. package/build/dist/Utils/Dashboard/Components/DashboardLogChartComponent.js.map +1 -0
  240. package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
  241. package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
  242. package/build/dist/Utils/Monitor/MonitorMetricType.js +70 -3
  243. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  244. 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: buildObservabilityChatSystemPrompt({
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 >= MAX_LLM_CALLS - 1 ||
108
- toolCallCount >= MAX_TOOL_CALLS ||
109
- Date.now() - startedAtMs >= MAX_WALL_CLOCK_MS;
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: MAX_OUTPUT_TOKENS,
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 >= MAX_TOOL_CALLS ||
156
- Date.now() - startedAtMs >= MAX_WALL_CLOCK_MS;
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).