@oneuptime/common 11.5.8 → 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 (195) hide show
  1. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +15 -6
  2. package/Models/DatabaseModels/AIRun.ts +37 -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 +182 -0
  12. package/Server/API/TelemetryExceptionAPI.ts +6 -1
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Services/AIRunEventService.ts +15 -1
  19. package/Server/Services/AIRunService.ts +38 -1
  20. package/Server/Services/LlmProviderService.ts +2 -0
  21. package/Server/Services/ProjectService.ts +42 -0
  22. package/Server/Services/StatusPageService.ts +87 -0
  23. package/Server/Services/TelemetryExceptionService.ts +19 -90
  24. package/Server/Types/Markdown.ts +9 -9
  25. package/Server/Types/MarkdownSlugify.ts +47 -0
  26. package/Server/Utils/AI/AIRunTranscript.ts +240 -0
  27. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
  28. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
  29. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
  30. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
  31. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
  32. package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
  33. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
  34. package/Server/Utils/AI/Toolbox/Index.ts +27 -0
  35. package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
  36. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
  37. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
  38. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
  39. package/Server/Utils/LLM/LLMService.ts +397 -18
  40. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
  41. package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
  42. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
  43. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
  44. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
  45. package/Tests/Server/Types/Markdown.test.ts +106 -0
  46. package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
  47. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
  48. package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
  49. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
  50. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
  51. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
  52. package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
  53. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
  54. package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
  55. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
  56. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
  57. package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
  58. package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
  59. package/Tests/Types/Log/LogSeverity.test.ts +78 -0
  60. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
  61. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
  62. package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
  63. package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
  64. package/Types/AI/AIAgentTaskStatus.ts +13 -0
  65. package/Types/AI/AIChatPageContext.ts +137 -0
  66. package/Types/AI/AIChatTypes.ts +44 -0
  67. package/Types/AI/AIFixReadiness.ts +46 -0
  68. package/Types/AI/AIRunStatus.ts +11 -0
  69. package/Types/Log/LogSeverity.ts +61 -0
  70. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
  71. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
  72. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
  73. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
  74. package/UI/Components/Forms/BasicForm.tsx +26 -8
  75. package/UI/Styles/Theme.css +1314 -0
  76. package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
  77. package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
  78. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
  79. package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
  80. package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
  81. package/UI/Utils/DownloadFile.ts +54 -0
  82. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
  83. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  84. package/build/dist/Models/DatabaseModels/AIRun.js +38 -0
  85. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  86. package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
  87. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
  88. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
  89. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  90. package/build/dist/Models/DatabaseModels/Project.js +27 -0
  91. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  92. package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
  93. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  94. package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
  95. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  96. package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
  97. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  98. package/build/dist/Server/API/AIChatAPI.js +9 -0
  99. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  100. package/build/dist/Server/API/AIReadinessAPI.js +57 -0
  101. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
  102. package/build/dist/Server/API/CodeFixRunAPI.js +142 -0
  103. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  104. package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
  105. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  116. package/build/dist/Server/Services/AIRunEventService.js +8 -0
  117. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  118. package/build/dist/Server/Services/AIRunService.js +29 -0
  119. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  120. package/build/dist/Server/Services/LlmProviderService.js +2 -0
  121. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  122. package/build/dist/Server/Services/ProjectService.js +39 -0
  123. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  124. package/build/dist/Server/Services/StatusPageService.js +81 -0
  125. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  126. package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
  127. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  128. package/build/dist/Server/Types/Markdown.js +8 -8
  129. package/build/dist/Server/Types/Markdown.js.map +1 -1
  130. package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
  131. package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
  132. package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
  133. package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
  134. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
  135. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  136. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
  137. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  138. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
  139. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  140. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
  141. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
  142. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  143. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
  144. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
  145. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
  146. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
  147. package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
  148. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  149. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
  150. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
  151. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
  152. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
  153. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
  154. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  155. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
  156. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
  157. package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
  158. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  159. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
  160. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  161. package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
  162. package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
  163. package/build/dist/Types/AI/AIChatPageContext.js +101 -0
  164. package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
  165. package/build/dist/Types/AI/AIChatTypes.js +2 -0
  166. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  167. package/build/dist/Types/AI/AIFixReadiness.js +9 -0
  168. package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
  169. package/build/dist/Types/AI/AIRunStatus.js +11 -0
  170. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  171. package/build/dist/Types/Log/LogSeverity.js +56 -0
  172. package/build/dist/Types/Log/LogSeverity.js.map +1 -1
  173. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
  174. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
  175. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
  176. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  177. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
  178. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  179. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
  180. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  181. package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
  182. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  183. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
  184. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
  185. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
  186. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
  187. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
  188. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
  189. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
  190. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
  191. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
  192. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
  193. package/build/dist/UI/Utils/DownloadFile.js +41 -0
  194. package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
  195. package/package.json +5 -1
@@ -12,12 +12,15 @@ import CodeFixTaskType, {
12
12
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
13
13
  import QueryHelper from "../Types/Database/QueryHelper";
14
14
  import CountBy from "../Types/Database/CountBy";
15
+ import CreateBy from "../Types/Database/CreateBy";
15
16
  import FindBy from "../Types/Database/FindBy";
16
- import { OnFind } from "../Types/Database/Hooks";
17
+ import { OnCreate, OnFind } from "../Types/Database/Hooks";
17
18
  import DatabaseService from "./DatabaseService";
19
+ import ProjectService from "./ProjectService";
18
20
  import Model from "../../Models/DatabaseModels/AIRun";
19
21
  import { applyAIRunPrivacyFilter } from "../Utils/AI/AIRunPrivacyFilter";
20
22
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
23
+ import logger from "../Utils/Logger";
21
24
  import { UpdateQueryBuilder, UpdateResult } from "typeorm";
22
25
  import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity";
23
26
 
@@ -328,6 +331,40 @@ export class Service extends DatabaseService<Model> {
328
331
  return { runId: run.id, verdict: data.verdict };
329
332
  }
330
333
 
334
+ /*
335
+ * Stamp CodeFix runs with the project's next task number, so every AI task
336
+ * has the short human-citable handle "#42" that incidents and alerts have.
337
+ *
338
+ * CodeFix only, on purpose: chat turns and investigations share this table
339
+ * but are not tasks, and numbering them would advance the counter on every
340
+ * Ask-AI message — the AI Tasks list would then read #7, #31, #244.
341
+ *
342
+ * Best-effort. The counter needs Redis (Semaphore) and a second write; a
343
+ * blip there must not lose a fix run, because the number is a display
344
+ * convenience and the run itself is the work. On failure the run is created
345
+ * with a null taskNumber, which the list renders as "-" and the detail page
346
+ * hides — the run stays fully usable, just uncitable.
347
+ */
348
+ protected override async onBeforeCreate(
349
+ createBy: CreateBy<Model>,
350
+ ): Promise<OnCreate<Model>> {
351
+ const projectId: ObjectID | undefined =
352
+ createBy.data.projectId || createBy.props.tenantId || undefined;
353
+
354
+ if (createBy.data.runType === AIRunType.CodeFix && projectId) {
355
+ try {
356
+ createBy.data.taskNumber =
357
+ await ProjectService.incrementAndGetAIRunCounter(projectId);
358
+ } catch (error) {
359
+ logger.error(
360
+ `Could not allocate a task number for a code-fix run in project ${projectId.toString()}; creating it unnumbered: ${error}`,
361
+ );
362
+ }
363
+ }
364
+
365
+ return { createBy, carryForward: null };
366
+ }
367
+
331
368
  protected override async onBeforeFind(
332
369
  findBy: FindBy<Model>,
333
370
  ): Promise<OnFind<Model>> {
@@ -333,6 +333,7 @@ export class Service extends DatabaseService<Model> {
333
333
  select: {
334
334
  _id: true,
335
335
  name: true,
336
+ description: true,
336
337
  llmType: true,
337
338
  modelName: true,
338
339
  isDefault: true,
@@ -356,6 +357,7 @@ export class Service extends DatabaseService<Model> {
356
357
  select: {
357
358
  _id: true,
358
359
  name: true,
360
+ description: true,
359
361
  llmType: true,
360
362
  modelName: true,
361
363
  isDefault: true,
@@ -2144,6 +2144,48 @@ export class ProjectService extends DatabaseService<Model> {
2144
2144
  }
2145
2145
  }
2146
2146
 
2147
+ /*
2148
+ * Allocate the next AI task number for a project. Unlike the counters
2149
+ * above there is no companion prefix column — AI task numbers are not
2150
+ * user-customizable, so the caller renders a plain "#N".
2151
+ */
2152
+ @CaptureSpan()
2153
+ public async incrementAndGetAIRunCounter(
2154
+ projectId: ObjectID,
2155
+ ): Promise<number> {
2156
+ const mutex: SemaphoreMutex = await Semaphore.lock({
2157
+ key: projectId.toString(),
2158
+ namespace: "ProjectService.aiRunCounter",
2159
+ });
2160
+
2161
+ try {
2162
+ await this.atomicIncrementColumnValueByOne({
2163
+ id: projectId,
2164
+ columnName: "aiRunCounter",
2165
+ });
2166
+
2167
+ const project: Model | null = await this.findOneById({
2168
+ id: projectId,
2169
+ select: {
2170
+ aiRunCounter: true,
2171
+ },
2172
+ props: {
2173
+ isRoot: true,
2174
+ },
2175
+ });
2176
+
2177
+ if (!project || project.aiRunCounter === undefined) {
2178
+ throw new BadDataException(
2179
+ `Could not read aiRunCounter for project ${projectId.toString()}`,
2180
+ );
2181
+ }
2182
+
2183
+ return project.aiRunCounter;
2184
+ } finally {
2185
+ await Semaphore.release(mutex);
2186
+ }
2187
+ }
2188
+
2147
2189
  @CaptureSpan()
2148
2190
  public async isSMSNotificationsEnabled(
2149
2191
  projectId: ObjectID,
@@ -118,6 +118,93 @@ export class Service extends DatabaseService<StatusPage> {
118
118
  this.statusPageUrlCache.clear();
119
119
  }
120
120
 
121
+ /*
122
+ * Mirrors `resolveStatusPageIdOrThrow` in `Common/Server/API/StatusPageAPI.ts`
123
+ * (module-private there), but returns null instead of throwing so callers can
124
+ * collapse "no such page" and "page exists but is gated" into one answer.
125
+ * The dot check — not a UUID check — is the discriminator, so both resolvers
126
+ * agree on dotless hostnames like "localhost".
127
+ */
128
+ @CaptureSpan()
129
+ public async resolveStatusPageIdOrNull(
130
+ statusPageIdOrDomain: string,
131
+ ): Promise<ObjectID | null> {
132
+ if (!statusPageIdOrDomain) {
133
+ return null;
134
+ }
135
+
136
+ if (statusPageIdOrDomain.includes(".")) {
137
+ const statusPageDomain: StatusPageDomain | null =
138
+ await StatusPageDomainService.findOneBy({
139
+ query: {
140
+ fullDomain: statusPageIdOrDomain,
141
+ domain: {
142
+ isVerified: true,
143
+ } as any,
144
+ },
145
+ select: {
146
+ statusPageId: true,
147
+ },
148
+ props: {
149
+ isRoot: true,
150
+ },
151
+ });
152
+
153
+ if (!statusPageDomain || !statusPageDomain.statusPageId) {
154
+ return null;
155
+ }
156
+
157
+ return statusPageDomain.statusPageId;
158
+ }
159
+
160
+ try {
161
+ ObjectID.validateUUID(statusPageIdOrDomain);
162
+ return new ObjectID(statusPageIdOrDomain);
163
+ } catch (err) {
164
+ logger.error(
165
+ `Error converting statusPageIdOrDomain to ObjectID: ${statusPageIdOrDomain}`,
166
+ );
167
+ logger.error(err);
168
+ return null;
169
+ }
170
+ }
171
+
172
+ /*
173
+ * Folds existence and the `enableMcpServer` flag into a single query so the
174
+ * caller cannot distinguish "not found" from "disabled". The public MCP tools
175
+ * need no API key, so a distinct "disabled" message would be a status page
176
+ * enumeration oracle.
177
+ *
178
+ * Throws on database failure rather than returning false, so the caller
179
+ * reports an error instead of reporting the page as gated.
180
+ */
181
+ @CaptureSpan()
182
+ public async isMcpServerEnabled(
183
+ statusPageIdOrDomain: string,
184
+ ): Promise<boolean> {
185
+ const statusPageId: ObjectID | null =
186
+ await this.resolveStatusPageIdOrNull(statusPageIdOrDomain);
187
+
188
+ if (!statusPageId) {
189
+ return false;
190
+ }
191
+
192
+ const statusPage: StatusPage | null = await this.findOneBy({
193
+ query: {
194
+ _id: statusPageId,
195
+ enableMcpServer: true,
196
+ },
197
+ select: {
198
+ _id: true,
199
+ },
200
+ props: {
201
+ isRoot: true,
202
+ },
203
+ });
204
+
205
+ return Boolean(statusPage);
206
+ }
207
+
121
208
  public static getDefaultEmailFooterText(): string {
122
209
  return "This is an automated email sent to you because you are subscribed to this Status Page.";
123
210
  }
@@ -13,20 +13,18 @@ import CodeFixTaskType, {
13
13
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
14
14
  import AIRunService from "./AIRunService";
15
15
  import FixRunBudget from "../Utils/AI/CodeFix/FixRunBudget";
16
- import AIAgentService from "./AIAgentService";
17
- import LlmProviderService from "./LlmProviderService";
18
- import ProjectService from "./ProjectService";
16
+ import CodeFixReadiness from "../Utils/AI/CodeFix/CodeFixReadiness";
19
17
  import ServiceService from "./ServiceService";
20
18
  import CodeRepositoryService from "./CodeRepositoryService";
21
19
  import { RepoResolution } from "../Utils/CodeRepository/StackTraceRepoResolver";
22
- import AIAgent from "../../Models/DatabaseModels/AIAgent";
23
- import LlmProvider from "../../Models/DatabaseModels/LlmProvider";
24
- import Project from "../../Models/DatabaseModels/Project";
20
+ import {
21
+ AIFixReadiness,
22
+ AIFixReadinessCheck,
23
+ } from "../../Types/AI/AIFixReadiness";
25
24
  import TelemetryService from "../../Models/DatabaseModels/Service";
26
25
  import QueryHelper from "../Types/Database/QueryHelper";
27
26
  import ModelPermission from "../Types/Database/Permissions/Index";
28
27
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
29
- import { IsBillingEnabled } from "../EnvironmentConfig";
30
28
  import Query from "../Types/Database/Query";
31
29
  import Includes from "../../Types/BaseDatabase/Includes";
32
30
  import logger from "../Utils/Logger";
@@ -46,24 +44,6 @@ export interface CreateCodeFixRunForExceptionParams {
46
44
  taskType?: CodeFixTaskType | undefined;
47
45
  }
48
46
 
49
- export type AIFixReadinessCheckId =
50
- | "llmProvider"
51
- | "repositoryResolved"
52
- | "agentAvailable";
53
-
54
- export interface AIFixReadinessCheck {
55
- id: AIFixReadinessCheckId;
56
- ok: boolean;
57
- title: string;
58
- // Shown to the user when ok is false — says exactly what to do next.
59
- detail: string;
60
- }
61
-
62
- export interface AIFixReadiness {
63
- ready: boolean;
64
- checks: Array<AIFixReadinessCheck>;
65
- }
66
-
67
47
  export interface DashboardServiceSummary {
68
48
  /*
69
49
  * Polymorphic: a real Service, a Host/DockerHost/KubernetesCluster id, or
@@ -124,55 +104,16 @@ export class Service extends DatabaseService<Model> {
124
104
  const projectId: ObjectID = telemetryException.projectId;
125
105
 
126
106
  /*
127
- * 1. An LLM provider the agent may use. Agent completions are
128
- * server-mediated and metered (B4 Tier 0), so the shared global
129
- * provider is a valid fallback on cloud too — a project-owned provider
130
- * simply wins when one exists.
131
- */
132
- const llmProvider: LlmProvider | null =
133
- await LlmProviderService.getLlmProviderForMeteredAgentPath(projectId);
134
-
135
- let llmOk: boolean = Boolean(llmProvider);
136
- let llmDetail: string = llmOk
137
- ? ""
138
- : "AI fix tasks need an LLM provider. Add one in Project Settings > AI > LLM Providers. Self-hosted instances can alternatively set the GLOBAL_LLM_PROVIDER_* environment variables to register a global provider for every project.";
139
-
140
- /*
141
- * A resolved provider must also be PAYABLE, or the run passes readiness
142
- * here and dies at its first completion call instead. Mirrors the
143
- * billing gate in AIService.executeWithLogging: only a COSTED global
144
- * provider on cloud bills the project's AI balance per call —
145
- * project-owned and free-global providers consume no balance and must
146
- * not require one.
107
+ * 1. An LLM provider the agent may use the same gate the project-wide
108
+ * AI Tasks page renders, so the two surfaces can never disagree about
109
+ * whether this project has a usable provider.
147
110
  */
148
- const billingEnabled: boolean = params.billingEnabled ?? IsBillingEnabled;
149
-
150
- if (
151
- llmProvider &&
152
- billingEnabled &&
153
- (llmProvider.isGlobalLlm || false) &&
154
- (llmProvider.costPerMillionTokensInUSDCents || 0) > 0
155
- ) {
156
- const project: Project | null = await ProjectService.findOneById({
157
- id: projectId,
158
- select: { aiCurrentBalanceInUSDCents: true },
159
- props: { isRoot: true },
111
+ const llmCheck: AIFixReadinessCheck =
112
+ await CodeFixReadiness.getLlmProviderCheck({
113
+ projectId,
114
+ billingEnabled: params.billingEnabled,
160
115
  });
161
116
 
162
- if (!project || (project.aiCurrentBalanceInUSDCents || 0) <= 0) {
163
- llmOk = false;
164
- llmDetail =
165
- "AI fix tasks would use the OneUptime-hosted LLM provider, which is billed against your AI balance — and the project's balance is empty. Recharge it in Project Settings > AI Credits, or add your own LLM provider in Project Settings > AI > LLM Providers.";
166
- }
167
- }
168
-
169
- const llmCheck: AIFixReadinessCheck = {
170
- id: "llmProvider",
171
- ok: llmOk,
172
- title: "LLM provider",
173
- detail: llmDetail,
174
- };
175
-
176
117
  /*
177
118
  * 2. A repository must RESOLVE for this exception — computed at runtime
178
119
  * from the stack trace (path matching over connected repos), falling
@@ -223,25 +164,11 @@ export class Service extends DatabaseService<Model> {
223
164
  };
224
165
  }
225
166
 
226
- // 4. An agent must be alive to pick the task up.
227
- const anyAgent: AIAgent | null =
228
- await AIAgentService.getAIAgentForProject(projectId);
229
- const connectedAgent: AIAgent | null = anyAgent
230
- ? AIAgentService.isAgentAlive(anyAgent)
231
- ? anyAgent
232
- : null
233
- : null;
234
-
235
- const agentCheck: AIFixReadinessCheck = {
236
- id: "agentAvailable",
237
- ok: Boolean(connectedAgent),
238
- title: "AI agent online",
239
- detail: connectedAgent
240
- ? ""
241
- : anyAgent
242
- ? `The AI agent "${anyAgent.name || "agent"}" has not reported in — check that its container is running.`
243
- : "No AI agent is available for this project. Self-hosted: create an agent under Settings > AI > AI Agents and run its container. Cloud: the shared fleet appears here automatically once enabled.",
244
- };
167
+ // 3. An agent must be alive to pick the task up.
168
+ const agentCheck: AIFixReadinessCheck =
169
+ await CodeFixReadiness.getAgentCheck({
170
+ projectId,
171
+ });
245
172
 
246
173
  const checks: Array<AIFixReadinessCheck> = [
247
174
  llmCheck,
@@ -387,8 +314,10 @@ export class Service extends DatabaseService<Model> {
387
314
  taskType === CodeFixTaskType.FixException
388
315
  ? QueryHelper.equalToOrNull(CodeFixTaskType.FixException)
389
316
  : taskType,
317
+ // Every terminal status — see AIRunStatusHelper.isTerminalStatus.
390
318
  status: QueryHelper.notIn([
391
319
  AIRunStatus.Completed,
320
+ AIRunStatus.NoFixFound,
392
321
  AIRunStatus.Error,
393
322
  AIRunStatus.Cancelled,
394
323
  AIRunStatus.Stale,
@@ -1,5 +1,6 @@
1
1
  import { Renderer, marked } from "marked";
2
2
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
3
+ import markdownSlugify from "./MarkdownSlugify";
3
4
 
4
5
  export type MarkdownRenderer = Renderer;
5
6
 
@@ -235,15 +236,14 @@ export default class Markdown {
235
236
  return renderer;
236
237
  }
237
238
 
238
- private static slugify(text: string): string {
239
- return text
240
- .toLowerCase()
241
- .replace(/<[^>]*>/g, "")
242
- .replace(/&[^;]+;/g, "")
243
- .replace(/[^\w\s-]/g, "")
244
- .replace(/\s+/g, "-")
245
- .replace(/-+/g, "-")
246
- .replace(/^-|-$/g, "");
239
+ /*
240
+ * Heading text -> the `id` used for in-page anchors. The rules live in
241
+ * MarkdownSlugify.ts, a dependency-free module, so the docs anchor scripts
242
+ * (Scripts/Docs/CheckAnchors.ts, FixAnchors.ts) can share them without
243
+ * pulling in marked or the telemetry stack — see the comment there.
244
+ */
245
+ public static slugify(text: string): string {
246
+ return markdownSlugify(text);
247
247
  }
248
248
 
249
249
  private static getDocsRenderer(): Renderer {
@@ -0,0 +1,47 @@
1
+ /*
2
+ * Heading text -> the `id` used for in-page anchors.
3
+ *
4
+ * This is the single source of truth for anchor ids. The docs renderer
5
+ * (Common/Server/Types/Markdown.ts) exposes it as Markdown.slugify, and the
6
+ * docs anchor scripts (Scripts/Docs/CheckAnchors.ts, Scripts/Docs/FixAnchors.ts)
7
+ * import it directly; a second copy of these rules would drift and then pass a
8
+ * link the renderer serves as a 404.
9
+ *
10
+ * It lives in its own module with ZERO imports on purpose: Markdown.ts pulls in
11
+ * marked and the telemetry stack, which only resolve inside a fully installed
12
+ * package. The scripts run in CI with nothing but the root npm install, so they
13
+ * must be able to load the slugify rules without any of that.
14
+ *
15
+ * The character class keeps letters and numbers in ANY script, via \p{L} and
16
+ * \p{N} with the /u flag. It used to be `\w`, which is ASCII-only — so every
17
+ * Cyrillic, CJK and Devanagari heading was stripped to nothing and rendered
18
+ * as id="", breaking every anchor in those locales. Accented Latin lost the
19
+ * accented letter alone ("gravité" -> "gravit").
20
+ *
21
+ * \p{M} (combining marks) is required alongside \p{L}: Devanagari matras and
22
+ * Arabic/Thai vowel signs are marks, not letters, so without it Hindi headings
23
+ * come out mangled rather than merely transliterated ("लॉग" -> "लग"). It also
24
+ * covers decomposed (NFD) Latin, where "é" is "e" + a combining accent.
25
+ *
26
+ * Variation selectors are dropped first. They are marks too, so \p{M} would
27
+ * otherwise keep them — and an emoji heading like "⚠️ Important" carries an
28
+ * invisible U+FE0F that would survive its emoji and lead the id.
29
+ *
30
+ * `_` is listed explicitly because \p{L}/\p{N} do not cover it but the old
31
+ * `\w` did, and real headings depend on it — e.g. "`ceph_health_status` is 1
32
+ * but no incident fires". Dropping it would silently renumber existing
33
+ * English anchors and break inbound deep links.
34
+ */
35
+ const slugify: (text: string) => string = (text: string): string => {
36
+ return text
37
+ .toLowerCase()
38
+ .replace(/<[^>]*>/g, "")
39
+ .replace(/&[^;]+;/g, "")
40
+ .replace(/[\uFE00-\uFE0F\u{E0100}-\u{E01EF}]/gu, "")
41
+ .replace(/[^\p{L}\p{N}\p{M}_\s-]/gu, "")
42
+ .replace(/\s+/g, "-")
43
+ .replace(/-+/g, "-")
44
+ .replace(/^-|-$/g, "");
45
+ };
46
+
47
+ export default slugify;