@oneuptime/common 11.5.7 → 11.5.9

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 (212) hide show
  1. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +15 -6
  2. package/Models/DatabaseModels/AIRun.ts +68 -0
  3. package/Models/DatabaseModels/AIRunEvent.ts +34 -1
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
  5. package/Models/DatabaseModels/Project.ts +25 -0
  6. package/Models/DatabaseModels/StatusPage.ts +42 -0
  7. package/Server/API/AIAgentTaskAPI.ts +38 -12
  8. package/Server/API/AIAgentTaskLogAPI.ts +49 -0
  9. package/Server/API/AIChatAPI.ts +16 -0
  10. package/Server/API/AIReadinessAPI.ts +84 -0
  11. package/Server/API/CodeFixRunAPI.ts +190 -57
  12. package/Server/API/TelemetryExceptionAPI.ts +6 -1
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  19. package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
  20. package/Server/Middleware/ProjectAuthorization.ts +1 -1
  21. package/Server/Services/AIRunEventService.ts +15 -1
  22. package/Server/Services/AIRunService.ts +48 -12
  23. package/Server/Services/LlmProviderService.ts +2 -0
  24. package/Server/Services/ProjectService.ts +42 -0
  25. package/Server/Services/StatusPageService.ts +87 -0
  26. package/Server/Services/TelemetryExceptionService.ts +19 -90
  27. package/Server/Types/Markdown.ts +9 -9
  28. package/Server/Types/MarkdownSlugify.ts +47 -0
  29. package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
  30. package/Server/Utils/AI/AIRunTranscript.ts +240 -0
  31. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
  32. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
  33. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
  34. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
  35. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
  36. package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
  37. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
  38. package/Server/Utils/AI/Toolbox/Index.ts +27 -0
  39. package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
  40. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
  41. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
  42. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
  43. package/Server/Utils/LLM/LLMService.ts +397 -18
  44. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
  45. package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
  46. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
  47. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
  48. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
  49. package/Tests/Server/Types/Markdown.test.ts +106 -0
  50. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
  51. package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
  52. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
  53. package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
  54. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
  55. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
  56. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
  57. package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
  58. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
  59. package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
  60. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
  61. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
  62. package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
  63. package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
  64. package/Tests/Types/Log/LogSeverity.test.ts +78 -0
  65. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
  66. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
  67. package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
  68. package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
  69. package/Types/AI/AIAgentTaskStatus.ts +13 -0
  70. package/Types/AI/AIChatPageContext.ts +137 -0
  71. package/Types/AI/AIChatTypes.ts +44 -0
  72. package/Types/AI/AIFixReadiness.ts +46 -0
  73. package/Types/AI/AIRunStatus.ts +11 -0
  74. package/Types/Email/EmailTemplateType.ts +2 -0
  75. package/Types/Log/LogSeverity.ts +61 -0
  76. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
  77. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
  78. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
  79. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
  80. package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
  81. package/UI/Components/Forms/BasicForm.tsx +26 -8
  82. package/UI/Styles/Theme.css +1314 -0
  83. package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
  84. package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
  85. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
  86. package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
  87. package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
  88. package/UI/Utils/DownloadFile.ts +54 -0
  89. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
  90. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  91. package/build/dist/Models/DatabaseModels/AIRun.js +69 -0
  92. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  93. package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
  94. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
  95. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
  96. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/Project.js +27 -0
  98. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
  100. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  101. package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
  102. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  103. package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
  104. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  105. package/build/dist/Server/API/AIChatAPI.js +9 -0
  106. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  107. package/build/dist/Server/API/AIReadinessAPI.js +57 -0
  108. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
  109. package/build/dist/Server/API/CodeFixRunAPI.js +165 -51
  110. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  111. package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
  112. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  125. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
  126. package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
  127. package/build/dist/Server/Services/AIRunEventService.js +8 -0
  128. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  129. package/build/dist/Server/Services/AIRunService.js +39 -3
  130. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  131. package/build/dist/Server/Services/LlmProviderService.js +2 -0
  132. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  133. package/build/dist/Server/Services/ProjectService.js +39 -0
  134. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  135. package/build/dist/Server/Services/StatusPageService.js +81 -0
  136. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  137. package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
  138. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  139. package/build/dist/Server/Types/Markdown.js +8 -8
  140. package/build/dist/Server/Types/Markdown.js.map +1 -1
  141. package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
  142. package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
  143. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
  144. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
  145. package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
  146. package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
  147. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
  148. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  149. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
  150. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  151. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
  152. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  153. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
  154. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
  155. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  156. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
  157. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
  158. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
  159. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
  160. package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
  161. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  162. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
  163. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
  164. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
  165. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
  166. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
  167. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  168. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
  169. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
  170. package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
  171. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
  173. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  174. package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
  175. package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
  176. package/build/dist/Types/AI/AIChatPageContext.js +101 -0
  177. package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
  178. package/build/dist/Types/AI/AIChatTypes.js +2 -0
  179. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  180. package/build/dist/Types/AI/AIFixReadiness.js +9 -0
  181. package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
  182. package/build/dist/Types/AI/AIRunStatus.js +11 -0
  183. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  184. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  185. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  186. package/build/dist/Types/Log/LogSeverity.js +56 -0
  187. package/build/dist/Types/Log/LogSeverity.js.map +1 -1
  188. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
  189. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
  190. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
  191. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
  193. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
  195. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  196. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
  197. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  198. package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
  199. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  200. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
  201. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
  202. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
  203. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
  204. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
  205. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
  206. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
  207. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
  208. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
  209. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
  210. package/build/dist/UI/Utils/DownloadFile.js +41 -0
  211. package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
  212. package/package.json +5 -1
@@ -0,0 +1,188 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ AIChatToolActionStatus,
4
+ AIChatWidget,
5
+ AIChatWidgetType,
6
+ } from "../../../../Types/AI/AIChatTypes";
7
+ import { JSONObject } from "../../../../Types/JSON";
8
+ import {
9
+ citationsToMarkdown,
10
+ toolActionsToMarkdown,
11
+ widgetToMarkdown,
12
+ } from "../../../../UI/Utils/AIChatExport/ChatWidgetMarkdown";
13
+
14
+ /*
15
+ * The chat caps how much of a widget it shows, and says so. An export that
16
+ * quietly dropped the overflow — or quietly included it — would misrepresent
17
+ * the answer either way, so the caps and their captions are pinned here.
18
+ */
19
+
20
+ function rows(count: number): Array<JSONObject> {
21
+ return Array.from({ length: count }, (_unused: unknown, index: number) => {
22
+ return { name: `row-${index}` };
23
+ });
24
+ }
25
+
26
+ describe("ChatWidgetMarkdown", () => {
27
+ describe("widgetToMarkdown", () => {
28
+ test("renders the title and description", () => {
29
+ const markdown: string = widgetToMarkdown({
30
+ id: "W1",
31
+ type: AIChatWidgetType.StatCards,
32
+ title: "Summary",
33
+ description: "Last hour",
34
+ data: { stats: [{ label: "Errors", value: 3 }] },
35
+ });
36
+
37
+ expect(markdown).toContain("#### Summary");
38
+ expect(markdown).toContain("_Last hour_");
39
+ expect(markdown).toContain("| Errors | 3 |");
40
+ });
41
+
42
+ test("caps table rows at 25 and says how many were shown", () => {
43
+ const widget: AIChatWidget = {
44
+ id: "W1",
45
+ type: AIChatWidgetType.Table,
46
+ title: "Rows",
47
+ data: {
48
+ columns: [{ key: "name", title: "Name" }],
49
+ rows: rows(40),
50
+ },
51
+ };
52
+
53
+ const markdown: string = widgetToMarkdown(widget);
54
+
55
+ expect(markdown).toContain("row-24");
56
+ expect(markdown).not.toContain("row-25");
57
+ expect(markdown).toContain("_Showing 25 of 40 rows._");
58
+ });
59
+
60
+ test("caps entity lists at 12", () => {
61
+ const widget: AIChatWidget = {
62
+ id: "W1",
63
+ type: AIChatWidgetType.IncidentList,
64
+ title: "Incidents",
65
+ data: {
66
+ items: Array.from({ length: 20 }, (_u: unknown, i: number) => {
67
+ return { title: `incident-${i}` };
68
+ }),
69
+ },
70
+ };
71
+
72
+ const markdown: string = widgetToMarkdown(widget);
73
+
74
+ expect(markdown).toContain("incident-11");
75
+ expect(markdown).not.toContain("incident-12");
76
+ expect(markdown).toContain("_Showing 12 of 20._");
77
+ });
78
+
79
+ test("escapes a pipe so it cannot break the table", () => {
80
+ const markdown: string = widgetToMarkdown({
81
+ id: "W1",
82
+ type: AIChatWidgetType.Table,
83
+ title: "Rows",
84
+ data: {
85
+ columns: [{ key: "name", title: "Name" }],
86
+ rows: [{ name: "a|b" }],
87
+ },
88
+ });
89
+
90
+ expect(markdown).toContain("a\\|b");
91
+ });
92
+
93
+ test("empty widgets say so rather than rendering an empty table", () => {
94
+ expect(
95
+ widgetToMarkdown({
96
+ id: "W1",
97
+ type: AIChatWidgetType.Table,
98
+ title: "Rows",
99
+ data: { columns: [], rows: [] },
100
+ }),
101
+ ).toContain("_No rows._");
102
+
103
+ expect(
104
+ widgetToMarkdown({
105
+ id: "W2",
106
+ type: AIChatWidgetType.TimeSeriesChart,
107
+ title: "Chart",
108
+ data: { series: [] },
109
+ }),
110
+ ).toContain("_No data points in this range._");
111
+
112
+ expect(
113
+ widgetToMarkdown({
114
+ id: "W3",
115
+ type: AIChatWidgetType.IncidentList,
116
+ title: "Incidents",
117
+ data: { items: [] },
118
+ }),
119
+ ).toContain("_Nothing here._");
120
+ });
121
+ });
122
+
123
+ describe("toolActionsToMarkdown", () => {
124
+ test("leads with the status so a denied action cannot read as a done one", () => {
125
+ const markdown: string = toolActionsToMarkdown([
126
+ {
127
+ id: "t1",
128
+ toolName: "restart",
129
+ title: "Restart payment-svc",
130
+ arguments: { service: "payment-svc" },
131
+ isMutation: true,
132
+ requiresApproval: true,
133
+ status: AIChatToolActionStatus.Denied,
134
+ },
135
+ ]);
136
+
137
+ expect(markdown).toContain("- **Denied** — Restart payment-svc");
138
+ expect(markdown).toContain("service: payment-svc");
139
+ });
140
+
141
+ test("is empty when there are no actions", () => {
142
+ expect(toolActionsToMarkdown([])).toBe("");
143
+ });
144
+ });
145
+
146
+ describe("citationsToMarkdown", () => {
147
+ test("spells out a zero row count as evidence of absence", () => {
148
+ const markdown: string = citationsToMarkdown([
149
+ {
150
+ id: "C1",
151
+ toolName: "logs.query",
152
+ label: "Logs for payment-svc",
153
+ queryArguments: { service: "payment-svc" },
154
+ rowCount: 0,
155
+ },
156
+ ]);
157
+
158
+ expect(markdown).toContain("checked, found nothing");
159
+ expect(markdown).toContain('{"service":"payment-svc"}');
160
+ });
161
+
162
+ test("pluralizes a real row count", () => {
163
+ expect(
164
+ citationsToMarkdown([
165
+ {
166
+ id: "C1",
167
+ toolName: "t",
168
+ label: "l",
169
+ queryArguments: {},
170
+ rowCount: 1,
171
+ },
172
+ ]),
173
+ ).toContain("1 row");
174
+
175
+ expect(
176
+ citationsToMarkdown([
177
+ {
178
+ id: "C2",
179
+ toolName: "t",
180
+ label: "l",
181
+ queryArguments: {},
182
+ rowCount: 5,
183
+ },
184
+ ]),
185
+ ).toContain("5 rows");
186
+ });
187
+ });
188
+ });
@@ -2,6 +2,12 @@ enum AIAgentTaskStatus {
2
2
  Scheduled = "Scheduled",
3
3
  InProgress = "InProgress",
4
4
  Completed = "Completed",
5
+ /*
6
+ * The agent finished cleanly but had no fix to propose, so it opened no
7
+ * pull request. Reported instead of Error so a negative result is not
8
+ * counted as a failure — see AIRunStatus.NoFixFound, which this maps to.
9
+ */
10
+ NoFixFound = "NoFixFound",
5
11
  Error = "Error",
6
12
  }
7
13
 
@@ -32,6 +38,12 @@ export class AIAgentTaskStatusHelper {
32
38
  title: "Completed",
33
39
  description: "Task has been completed successfully.",
34
40
  },
41
+ {
42
+ status: AIAgentTaskStatus.NoFixFound,
43
+ title: "No Fix Found",
44
+ description:
45
+ "Task ran successfully but AI did not find a fix to propose.",
46
+ },
35
47
  {
36
48
  status: AIAgentTaskStatus.Error,
37
49
  title: "Error",
@@ -59,6 +71,7 @@ export class AIAgentTaskStatusHelper {
59
71
  public static isTerminalStatus(status: AIAgentTaskStatus): boolean {
60
72
  return (
61
73
  status === AIAgentTaskStatus.Completed ||
74
+ status === AIAgentTaskStatus.NoFixFound ||
62
75
  status === AIAgentTaskStatus.Error
63
76
  );
64
77
  }
@@ -0,0 +1,137 @@
1
+ import { JSONObject } from "../JSON";
2
+
3
+ /*
4
+ * "Page context" makes Ask AI aware of what the user is looking at in the
5
+ * dashboard. The client detects the current page (an incident, a monitor, a
6
+ * trace waterfall, the logs explorer, …) and attaches this small descriptor to
7
+ * each message it sends; the server folds it into the system prompt so "this
8
+ * incident" resolves to the entity on screen. It is a per-turn hint, never
9
+ * persisted — the user can detach it in the composer at any time.
10
+ */
11
+ enum AIChatPageContextType {
12
+ // Entity pages — carry the entity's id (and usually a display title).
13
+ Incident = "Incident",
14
+ Alert = "Alert",
15
+ Monitor = "Monitor",
16
+ ScheduledMaintenanceEvent = "ScheduledMaintenanceEvent",
17
+ TelemetryService = "TelemetryService",
18
+ Trace = "Trace",
19
+ Exception = "Exception",
20
+
21
+ // Area pages — no id; the user is browsing a list or an explorer.
22
+ IncidentsList = "IncidentsList",
23
+ AlertsList = "AlertsList",
24
+ MonitorsList = "MonitorsList",
25
+ ScheduledMaintenanceList = "ScheduledMaintenanceList",
26
+ LogsExplorer = "LogsExplorer",
27
+ TracesExplorer = "TracesExplorer",
28
+ MetricsExplorer = "MetricsExplorer",
29
+ ExceptionsList = "ExceptionsList",
30
+ }
31
+
32
+ export default AIChatPageContextType;
33
+
34
+ export interface AIChatPageContext {
35
+ type: AIChatPageContextType;
36
+ // The entity on screen (UUID, or a hex trace id). Only set for entity types.
37
+ entityId?: string | undefined;
38
+ // Display title of the entity (incident title, monitor name, …). Optional.
39
+ entityTitle?: string | undefined;
40
+ }
41
+
42
+ /*
43
+ * Ids are OneUptime ObjectIDs (UUIDs) — except traces, whose ids are
44
+ * OpenTelemetry hex strings. Anything else is rejected: the id is echoed into
45
+ * the LLM prompt and into tool suggestions, so it must never carry free-form
46
+ * text (e.g. a static route segment like "overview" that a :id URL wildcard
47
+ * can capture).
48
+ */
49
+ const UUID_REGEX: RegExp =
50
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
51
+ const TRACE_ID_REGEX: RegExp = /^[0-9a-fA-F]{8,64}$/;
52
+
53
+ const MAX_ENTITY_TITLE_LENGTH: number = 200;
54
+
55
+ const ENTITY_TYPES: Array<AIChatPageContextType> = [
56
+ AIChatPageContextType.Incident,
57
+ AIChatPageContextType.Alert,
58
+ AIChatPageContextType.Monitor,
59
+ AIChatPageContextType.ScheduledMaintenanceEvent,
60
+ AIChatPageContextType.TelemetryService,
61
+ AIChatPageContextType.Trace,
62
+ AIChatPageContextType.Exception,
63
+ ];
64
+
65
+ export class AIChatPageContextHelper {
66
+ public static isValidType(
67
+ value: string | undefined,
68
+ ): value is AIChatPageContextType {
69
+ return Object.values(AIChatPageContextType).includes(
70
+ value as AIChatPageContextType,
71
+ );
72
+ }
73
+
74
+ // Entity types point at one specific record and require an entityId.
75
+ public static isEntityType(type: AIChatPageContextType): boolean {
76
+ return ENTITY_TYPES.includes(type);
77
+ }
78
+
79
+ /*
80
+ * Validate and sanitize an untrusted client-supplied page context. Returns
81
+ * undefined when the payload is missing or invalid — page context is a hint,
82
+ * so a bad one is dropped rather than failing the message.
83
+ */
84
+ public static sanitize(
85
+ json: JSONObject | undefined | null,
86
+ ): AIChatPageContext | undefined {
87
+ if (!json || typeof json !== "object" || Array.isArray(json)) {
88
+ return undefined;
89
+ }
90
+
91
+ const type: string | undefined =
92
+ typeof json["type"] === "string" ? (json["type"] as string) : undefined;
93
+
94
+ if (!this.isValidType(type)) {
95
+ return undefined;
96
+ }
97
+
98
+ const context: AIChatPageContext = { type: type };
99
+
100
+ if (this.isEntityType(type)) {
101
+ const entityId: string =
102
+ typeof json["entityId"] === "string"
103
+ ? (json["entityId"] as string).trim()
104
+ : "";
105
+
106
+ const idRegex: RegExp =
107
+ type === AIChatPageContextType.Trace ? TRACE_ID_REGEX : UUID_REGEX;
108
+
109
+ // An entity context without a usable id is meaningless — drop it whole.
110
+ if (!idRegex.test(entityId)) {
111
+ return undefined;
112
+ }
113
+
114
+ context.entityId = entityId;
115
+
116
+ if (typeof json["entityTitle"] === "string") {
117
+ /*
118
+ * The title is echoed into the system prompt: collapse whitespace and
119
+ * control characters so it cannot introduce new lines or fake
120
+ * prompt structure, and cap the length.
121
+ */
122
+ const entityTitle: string = (json["entityTitle"] as string)
123
+ // eslint-disable-next-line no-control-regex
124
+ .replace(/[\u0000-\u001F\u007F]+/g, " ")
125
+ .replace(/\s+/g, " ")
126
+ .trim()
127
+ .substring(0, MAX_ENTITY_TITLE_LENGTH);
128
+
129
+ if (entityTitle) {
130
+ context.entityTitle = entityTitle;
131
+ }
132
+ }
133
+ }
134
+
135
+ return context;
136
+ }
137
+ }
@@ -22,6 +22,8 @@ export enum AIChatCitationTargetType {
22
22
  AlertView = "AlertView",
23
23
  Monitors = "Monitors",
24
24
  MonitorView = "MonitorView",
25
+ ScheduledMaintenanceEvents = "ScheduledMaintenanceEvents",
26
+ ScheduledMaintenanceView = "ScheduledMaintenanceView",
25
27
  }
26
28
 
27
29
  export interface AIChatCitationTarget {
@@ -197,6 +199,48 @@ export interface AIRunEventResultSummary {
197
199
  severity?: string | undefined;
198
200
  }
199
201
 
202
+ /*
203
+ * The verbatim content behind one run event — the raw material for debugging
204
+ * a bad run: exactly what was sent to the model, exactly what it said back,
205
+ * and exactly what a tool returned.
206
+ *
207
+ * This is the ONLY place a code-fix run's LLM content is persisted. It lives
208
+ * on AIRunEvent.contentPayload, whose read ACL is empty precisely because
209
+ * this content embeds customer source code: LlmLog deliberately redacts the
210
+ * same content (CodeFixAgentCompletion passes storeContentPreviews: false)
211
+ * because LlmLog is readable project-wide, and that redaction stays. Reach it
212
+ * only through GET /code-fix-run/logs, which gates on ProjectOwner or
213
+ * ProjectAdmin — the same "sensitive content behind a narrow endpoint" shape
214
+ * AIRun.pausedState and AIRun.taskContext already use.
215
+ *
216
+ * Every field is capped at write time (see AIRunTranscript.ts) so one run can
217
+ * never write an unbounded row.
218
+ */
219
+ export interface AIRunEventContentPayload {
220
+ // Messages newly sent to the model on this call — not the whole history.
221
+ requestMessages?: Array<{
222
+ role: string;
223
+ content: string;
224
+ }>;
225
+ // The model's verbatim reply.
226
+ responseContent?: string | undefined;
227
+ // Tool calls the model asked for, with their full arguments.
228
+ responseToolCalls?: Array<{
229
+ id?: string | undefined;
230
+ name: string;
231
+ arguments?: JSONObject | undefined;
232
+ }>;
233
+ // A tool's verbatim return value (for tool events).
234
+ toolResult?: string | undefined;
235
+ modelName?: string | undefined;
236
+ stopReason?: string | undefined;
237
+ promptTokens?: number | undefined;
238
+ completionTokens?: number | undefined;
239
+ totalTokens?: number | undefined;
240
+ // Set when any field above was clipped to its cap.
241
+ isTruncated?: boolean | undefined;
242
+ }
243
+
200
244
  // What was sent to which LLM during a run — the per-run egress manifest.
201
245
  export interface AIRunEgressManifestToolEntry {
202
246
  toolName: string;
@@ -0,0 +1,46 @@
1
+ /*
2
+ * What a CodeFix run needs before it can complete, as a wire contract shared
3
+ * by the server checks and every UI that renders them. Two surfaces consume
4
+ * this today: the per-exception "Fix with AI Agent" panel
5
+ * (GET /telemetry-exception/ai-fix-readiness/:id) and the project-wide AI
6
+ * Tasks page (POST /ai-readiness/code-fix).
7
+ */
8
+
9
+ /*
10
+ * The repository gate comes in two strengths, and they are deliberately
11
+ * distinct ids — a UI that conflates them would promise more than the check
12
+ * verified:
13
+ *
14
+ * - repositoryResolved: a repository matches THIS exception's stack trace.
15
+ * Per-exception, and the stronger claim.
16
+ * - repositoryConnected: the project has at least one GitHub-App-connected
17
+ * repository. Project-wide, and all a page-level check can honestly assert
18
+ * — it says nothing about whether any given exception will resolve.
19
+ */
20
+ export type AIFixReadinessCheckId =
21
+ | "llmProvider"
22
+ | "repositoryResolved"
23
+ | "repositoryConnected"
24
+ | "agentAvailable";
25
+
26
+ export interface AIFixReadinessCheck {
27
+ id: AIFixReadinessCheckId;
28
+ ok: boolean;
29
+ /*
30
+ * The gate's headline. Checks fill in what they actually found where that
31
+ * is useful ("Repository resolved: acme/api"), so this is not a fixed
32
+ * label per id.
33
+ */
34
+ title: string;
35
+ /*
36
+ * When ok is false: exactly what to do next. When ok is true: what is
37
+ * configured, so the user can tell a project-owned provider from the
38
+ * shared one without leaving the page. May be empty.
39
+ */
40
+ detail: string;
41
+ }
42
+
43
+ export interface AIFixReadiness {
44
+ ready: boolean;
45
+ checks: Array<AIFixReadinessCheck>;
46
+ }
@@ -18,6 +18,16 @@ enum AIRunStatus {
18
18
  */
19
19
  WaitingForApproval = "WaitingForApproval",
20
20
  Completed = "Completed",
21
+ /*
22
+ * The run did its job and concluded there was nothing to change — the agent
23
+ * read the code and found no fix worth proposing, so it opened no pull
24
+ * request. A negative result, NOT a failure: nothing broke, and retrying the
25
+ * same run unchanged would likely reach the same conclusion. Kept distinct
26
+ * from Error so a quiet "no fix" never lands in an error rate, an alert, or
27
+ * a red pill. Contrast with Error, which means the run could not finish
28
+ * (clone failed, LLM errored, no repository resolved).
29
+ */
30
+ NoFixFound = "NoFixFound",
21
31
  Error = "Error",
22
32
  Cancelled = "Cancelled",
23
33
  Stale = "Stale",
@@ -29,6 +39,7 @@ export class AIRunStatusHelper {
29
39
  public static isTerminalStatus(status: AIRunStatus): boolean {
30
40
  return (
31
41
  status === AIRunStatus.Completed ||
42
+ status === AIRunStatus.NoFixFound ||
32
43
  status === AIRunStatus.Error ||
33
44
  status === AIRunStatus.Cancelled ||
34
45
  status === AIRunStatus.Stale
@@ -84,6 +84,8 @@ enum EmailTemplateType {
84
84
 
85
85
  ProjectSubscriptionOverdue = "ProjectSubscriptionOverdue.hbs",
86
86
  Invoice = "Invoice.hbs",
87
+
88
+ ClickhouseCapacityWarning = "ClickhouseCapacityWarning.hbs",
87
89
  }
88
90
 
89
91
  export default EmailTemplateType;
@@ -8,4 +8,65 @@ enum LogSeverity {
8
8
  Fatal = "Fatal",
9
9
  }
10
10
 
11
+ /*
12
+ * The OTLP severity number stored alongside each level.
13
+ *
14
+ * These are the low end of each OTLP range (Trace 1-4, Debug 5-8, Info 9-12,
15
+ * Warn 13-16, Error 17-20, Fatal 21-24), so they are the inverse of the
16
+ * bucketing OtelLogsIngestService.getSeverityText applies on ingest. Anything
17
+ * that writes severityText must take the number from here too, or the row says
18
+ * one thing in its text and another in its number.
19
+ *
20
+ * https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
21
+ */
22
+ export const LogSeverityNumber: Record<LogSeverity, number> = {
23
+ [LogSeverity.Unspecified]: 0,
24
+ [LogSeverity.Trace]: 1,
25
+ [LogSeverity.Debug]: 5,
26
+ [LogSeverity.Information]: 9,
27
+ [LogSeverity.Warning]: 13,
28
+ [LogSeverity.Error]: 17,
29
+ [LogSeverity.Fatal]: 21,
30
+ };
31
+
32
+ /*
33
+ * Loose severity text -> the enum member actually stored on a log row.
34
+ *
35
+ * Only these seven strings ever reach the database: ingest discards whatever
36
+ * severityText a client sent and re-derives it from severityNumber. So a value
37
+ * that is merely severity-shaped ("INFO", "warn") matches nothing — and since
38
+ * `=` and `IN` are case-sensitive, it fails silently rather than erroring.
39
+ *
40
+ * This exists because the filter and severity-remapper dropdowns spent a long
41
+ * time emitting TRACE/DEBUG/INFO/WARNING/ERROR/FATAL. Those values are still
42
+ * sitting in saved pipeline configs, so the remapper normalises through here on
43
+ * the way in rather than trusting what was stored.
44
+ *
45
+ * Returns null for anything unrecognised. Callers decide what to do about it —
46
+ * guessing a severity is worse than admitting we cannot tell.
47
+ */
48
+ export function normalizeLogSeverity(text: string): LogSeverity | null {
49
+ const normalized: string = (text || "").trim().toUpperCase();
50
+
51
+ if (!normalized) {
52
+ return null;
53
+ }
54
+
55
+ const aliases: Record<string, LogSeverity> = {
56
+ UNSPECIFIED: LogSeverity.Unspecified,
57
+ TRACE: LogSeverity.Trace,
58
+ DEBUG: LogSeverity.Debug,
59
+ // INFO and WARN are the two the old dropdowns got wrong by more than case.
60
+ INFO: LogSeverity.Information,
61
+ INFORMATION: LogSeverity.Information,
62
+ WARN: LogSeverity.Warning,
63
+ WARNING: LogSeverity.Warning,
64
+ ERR: LogSeverity.Error,
65
+ ERROR: LogSeverity.Error,
66
+ FATAL: LogSeverity.Fatal,
67
+ };
68
+
69
+ return aliases[normalized] || null;
70
+ }
71
+
11
72
  export default LogSeverity;
@@ -4,4 +4,44 @@ enum SnmpVersion {
4
4
  V3 = "3",
5
5
  }
6
6
 
7
+ /*
8
+ * Two spellings exist, and they belong to two different layers:
9
+ *
10
+ * - These enum VALUES ("1"/"2c"/"3") are the probe-contract spelling.
11
+ * MonitorStepSnmpMonitor.snmpVersion is typed as this enum, and
12
+ * SnmpMonitor branches on it to pick the session type.
13
+ * - The KEYS ("V1"/"V2c"/"V3") are the stored spelling. The free-text
14
+ * snmpVersion column on NetworkDevice / NetworkDeviceDiscoveryScan holds
15
+ * these: it is what every form writes, what the column default is, and
16
+ * what the public API reference documents as the allowed values.
17
+ *
18
+ * parse() is the conversion at that seam, and it is the only correct way to
19
+ * read the column. Comparing the raw column against an enum member instead
20
+ * silently takes the wrong branch — a stored "V3" is not === SnmpVersion.V3
21
+ * ("3"), so a v3 device reads as v2c and gets polled in cleartext.
22
+ *
23
+ * parse() also accepts the value spelling and mixed case, so a row written by
24
+ * hand (direct API call, manual DB edit) can't strand a device in the wrong
25
+ * branch. No product path writes that spelling — the tolerance is a guard, not
26
+ * a sign that the column is expected to hold both.
27
+ */
28
+ export class SnmpVersionUtil {
29
+ public static parse(value: string | undefined | null): SnmpVersion {
30
+ switch ((value || "").trim().toLowerCase()) {
31
+ case "1":
32
+ case "v1":
33
+ return SnmpVersion.V1;
34
+ case "3":
35
+ case "v3":
36
+ return SnmpVersion.V3;
37
+ default:
38
+ return SnmpVersion.V2c;
39
+ }
40
+ }
41
+
42
+ public static isV3(value: string | undefined | null): boolean {
43
+ return SnmpVersionUtil.parse(value) === SnmpVersion.V3;
44
+ }
45
+ }
46
+
7
47
  export default SnmpVersion;
@@ -762,6 +762,7 @@ const AreaChart: React.ForwardRefExoticComponent<
762
762
  transform: "translate(0, 6)",
763
763
  fontSize: 10,
764
764
  fontWeight: 500,
765
+ fill: "var(--ou-chart-tick)",
765
766
  }}
766
767
  ticks={
767
768
  startEndOnly
@@ -802,6 +803,7 @@ const AreaChart: React.ForwardRefExoticComponent<
802
803
  transform: "translate(-4, 0)",
803
804
  fontSize: 10,
804
805
  fontWeight: 500,
806
+ fill: "var(--ou-chart-tick)",
805
807
  }}
806
808
  fill=""
807
809
  stroke=""
@@ -822,7 +824,7 @@ const AreaChart: React.ForwardRefExoticComponent<
822
824
  )}
823
825
  </YAxis>
824
826
  <Tooltip
825
- wrapperStyle={{ outline: "none" }}
827
+ wrapperStyle={{ outline: "none", zIndex: 10 }}
826
828
  isAnimationActive={true}
827
829
  animationDuration={100}
828
830
  cursor={{
@@ -1076,7 +1078,11 @@ const AreaChart: React.ForwardRefExoticComponent<
1076
1078
  <Label
1077
1079
  value="E"
1078
1080
  position="center"
1079
- fill="var(--ou-chart-marker-ring, #ffffff)"
1081
+ /*
1082
+ * Sits on the dot's purple, not on the surface, so it must
1083
+ * not follow the ring token that flips dark with the theme.
1084
+ */
1085
+ fill="#ffffff"
1080
1086
  fontSize={8}
1081
1087
  fontWeight={700}
1082
1088
  />
@@ -836,6 +836,7 @@ const BarChart: React.ForwardRefExoticComponent<
836
836
  layout !== "vertical" ? "translate(0, 6)" : undefined,
837
837
  fontSize: 10,
838
838
  fontWeight: 500,
839
+ fill: "var(--ou-chart-tick)",
839
840
  }}
840
841
  fill=""
841
842
  stroke=""
@@ -904,6 +905,7 @@ const BarChart: React.ForwardRefExoticComponent<
904
905
  : "translate(0, 0)",
905
906
  fontSize: 10,
906
907
  fontWeight: 500,
908
+ fill: "var(--ou-chart-tick)",
907
909
  }}
908
910
  {...(layout !== "vertical"
909
911
  ? {
@@ -940,7 +942,7 @@ const BarChart: React.ForwardRefExoticComponent<
940
942
  )}
941
943
  </YAxis>
942
944
  <Tooltip
943
- wrapperStyle={{ outline: "none" }}
945
+ wrapperStyle={{ outline: "none", zIndex: 10 }}
944
946
  isAnimationActive={true}
945
947
  animationDuration={100}
946
948
  cursor={{