@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
@@ -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;
@@ -0,0 +1,106 @@
1
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
2
+ import { FindWhereProperty } from "../../../Types/BaseDatabase/Query";
3
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
4
+ import AIRunType from "../../../Types/AI/AIRunType";
5
+ import Text from "../../../Types/Text";
6
+ import { combineWithPrivacyClause } from "../PrivacyFilterUtil";
7
+ import { Raw } from "typeorm";
8
+
9
+ /*
10
+ * AIRun mixes two kinds of row under one table:
11
+ *
12
+ * - Chat and investigation runs are PERSONAL. They hang off a conversation
13
+ * and carry the user's own prompt trail, so only their author may read
14
+ * them. This is what AIChatPrivacyFilter's flat pin protects, and that pin
15
+ * stays exactly as it is for AIConversation, AIConversationMessage and
16
+ * AIRunEvent.
17
+ * - CodeFix runs are a SHARED TEAM ARTIFACT: they are triggered off an
18
+ * exception or an incident, they produce a pull request, and their child
19
+ * AIAgentTaskPullRequest rows are already project-readable through the
20
+ * standard CRUD. `userId` on a CodeFix run is attribution only (who
21
+ * clicked "Fix with AI Agent") and is null for automatic triggers — it
22
+ * carries no conversational content.
23
+ *
24
+ * So AIRun cannot use that flat pin. It needs `runType = CodeFix OR userId =
25
+ * <caller>`, a CROSS-COLUMN disjunction, which a query object cannot express
26
+ * (query objects are conjunctions).
27
+ *
28
+ * The clause below is built ENTIRELY from server state and AND-ed onto the
29
+ * caller's query. It never READS query.runType to decide anything — that
30
+ * would be unsound, because onBeforeFind runs BEFORE QueryUtil.serializeQuery,
31
+ * so query.runType here is still a raw, client-controlled operator object
32
+ * whose .value/.toString() are identical across the safe and hostile cases:
33
+ * LessThan("CodeFix") stringifies to exactly "CodeFix" but compiles to
34
+ * `runType < 'CodeFix'`, which matches "Chat". Because the clause is forced, a
35
+ * caller-supplied runType filter can only ever NARROW it, and
36
+ * combineWithPrivacyClause fails closed on operator shapes it does not
37
+ * recognize.
38
+ *
39
+ * Modelled on Utils/Incident/IncidentPrivacyFilter, which solves the same
40
+ * "unrestricted rows for everyone, restricted rows only for their owner"
41
+ * shape.
42
+ */
43
+ export function getAIRunPrivacyRaw(
44
+ props: DatabaseCommonInteractionProps,
45
+ ): FindWhereProperty<any> | undefined {
46
+ if (props.isRoot || props.isMasterAdmin) {
47
+ return undefined;
48
+ }
49
+
50
+ /*
51
+ * The same hard gate the flat pin has, and it must stay. A caller with no
52
+ * user — a project API key, which gets userTenantAccessPermission but never
53
+ * userAuthorization (see ProjectAuthorization), so props.userId is
54
+ * undefined — has no personal scope. Today such a caller cannot read AIRun
55
+ * at all. Letting CodeFix rows through here would hand every API key holding
56
+ * ProjectMember the project's whole fix history: access that neither
57
+ * /ai-run nor /code-fix-run grants today. That is a separate product
58
+ * decision and must not ride in as a side effect of this filter.
59
+ */
60
+ if (!props.userId) {
61
+ throw new NotAuthorizedException(
62
+ "AI runs are personal and can only be accessed by the user who created them.",
63
+ );
64
+ }
65
+
66
+ const userIdRid: string = "aiRunUid_" + Text.generateRandomText(10);
67
+ const runTypeRid: string = "aiRunType_" + Text.generateRandomText(10);
68
+
69
+ return Raw(
70
+ (alias: string): string => {
71
+ return `(${alias} = :${runTypeRid} OR "AIRun"."userId" = :${userIdRid})`;
72
+ },
73
+ {
74
+ [runTypeRid]: AIRunType.CodeFix,
75
+ [userIdRid]: props.userId.toString(),
76
+ },
77
+ );
78
+ }
79
+
80
+ /*
81
+ * Applies the clause above on AIRun.runType. Project isolation is unaffected:
82
+ * @TenantColumn("projectId") independently forces projectId = <tenant>, so
83
+ * "CodeFix runs are readable" always means "in YOUR project".
84
+ */
85
+ export function applyAIRunPrivacyFilter<TQuery>(
86
+ query: TQuery,
87
+ props: DatabaseCommonInteractionProps,
88
+ ): TQuery {
89
+ const rawClause: FindWhereProperty<any> | undefined =
90
+ getAIRunPrivacyRaw(props);
91
+
92
+ if (!rawClause) {
93
+ return query;
94
+ }
95
+
96
+ if (!query) {
97
+ return { runType: rawClause } as unknown as TQuery;
98
+ }
99
+
100
+ (query as any).runType = combineWithPrivacyClause(
101
+ (query as any).runType,
102
+ rawClause,
103
+ );
104
+
105
+ return query;
106
+ }
@@ -0,0 +1,240 @@
1
+ import { AIRunEventContentPayload } from "../../../Types/AI/AIChatTypes";
2
+ import { JSONObject } from "../../../Types/JSON";
3
+
4
+ /*
5
+ * Write-time caps for AIRunEvent.contentPayload — the run transcript.
6
+ *
7
+ * The transcript exists to debug a bad run, so it stores content verbatim
8
+ * rather than summarized. That makes bounding it a storage requirement, not a
9
+ * nicety: a code-fix agent may read large files and run test suites that emit
10
+ * megabytes, and it does so up to 40 times per run.
11
+ *
12
+ * Only NEW messages are recorded per call (never the whole replayed history),
13
+ * so a run's transcript grows linearly with its turns rather than
14
+ * quadratically. These caps then bound each turn.
15
+ */
16
+ export const MAX_MESSAGE_CONTENT_CHARS: number = 20000;
17
+ export const MAX_RESPONSE_CONTENT_CHARS: number = 20000;
18
+ export const MAX_TOOL_RESULT_CHARS: number = 20000;
19
+ export const MAX_TOOL_ARGUMENT_CHARS: number = 10000;
20
+ // Belt and braces: a hard ceiling on the serialized payload.
21
+ export const MAX_PAYLOAD_CHARS: number = 200000;
22
+
23
+ const TRUNCATION_SUFFIX: string = "\n… [truncated]";
24
+
25
+ export default class AIRunTranscript {
26
+ /*
27
+ * Clip one string to a cap. Returns the value and whether it was clipped,
28
+ * so the payload can honestly report truncation to the reader rather than
29
+ * silently presenting a partial prompt as complete.
30
+ */
31
+ public static clip(
32
+ value: string | undefined,
33
+ maxChars: number,
34
+ ): { value: string | undefined; isTruncated: boolean } {
35
+ if (value === undefined || value === null) {
36
+ return { value: undefined, isTruncated: false };
37
+ }
38
+
39
+ if (value.length <= maxChars) {
40
+ return { value, isTruncated: false };
41
+ }
42
+
43
+ return {
44
+ value: value.substring(0, maxChars) + TRUNCATION_SUFFIX,
45
+ isTruncated: true,
46
+ };
47
+ }
48
+
49
+ /*
50
+ * Clip tool arguments by serializing them, so a single huge argument (a
51
+ * write_file `content`, say) cannot blow the row up. Unparseable or
52
+ * oversized arguments degrade to a marker object rather than being dropped
53
+ * — knowing a tool was called with arguments too big to store is itself a
54
+ * useful debugging signal.
55
+ */
56
+ public static clipArguments(args: JSONObject | undefined): {
57
+ value: JSONObject | undefined;
58
+ isTruncated: boolean;
59
+ } {
60
+ if (!args) {
61
+ return { value: undefined, isTruncated: false };
62
+ }
63
+
64
+ let serialized: string = "";
65
+
66
+ try {
67
+ serialized = JSON.stringify(args);
68
+ } catch {
69
+ return {
70
+ value: { _note: "Arguments could not be serialized." },
71
+ isTruncated: true,
72
+ };
73
+ }
74
+
75
+ if (serialized.length <= MAX_TOOL_ARGUMENT_CHARS) {
76
+ return { value: args, isTruncated: false };
77
+ }
78
+
79
+ return {
80
+ value: {
81
+ _note: `Arguments omitted — ${serialized.length} characters exceeds the ${MAX_TOOL_ARGUMENT_CHARS} character cap.`,
82
+ _preview: serialized.substring(0, 2000),
83
+ },
84
+ isTruncated: true,
85
+ };
86
+ }
87
+
88
+ /*
89
+ * Apply every cap to a payload and stamp isTruncated if anything was
90
+ * clipped. The final MAX_PAYLOAD_CHARS check is a backstop for a payload
91
+ * that is under the per-field caps but still large in aggregate (many
92
+ * messages); it drops the request messages first, since the response and
93
+ * tool result are the more valuable half when debugging.
94
+ */
95
+ public static clampPayload(
96
+ payload: AIRunEventContentPayload,
97
+ ): AIRunEventContentPayload {
98
+ let isTruncated: boolean = payload.isTruncated === true;
99
+
100
+ const clamped: AIRunEventContentPayload = { ...payload };
101
+
102
+ if (payload.requestMessages) {
103
+ clamped.requestMessages = payload.requestMessages.map(
104
+ (message: { role: string; content: string }) => {
105
+ const clipped: { value: string | undefined; isTruncated: boolean } =
106
+ this.clip(message.content, MAX_MESSAGE_CONTENT_CHARS);
107
+
108
+ if (clipped.isTruncated) {
109
+ isTruncated = true;
110
+ }
111
+
112
+ return { role: message.role, content: clipped.value || "" };
113
+ },
114
+ );
115
+ }
116
+
117
+ if (payload.responseContent !== undefined) {
118
+ const clipped: { value: string | undefined; isTruncated: boolean } =
119
+ this.clip(payload.responseContent, MAX_RESPONSE_CONTENT_CHARS);
120
+ clamped.responseContent = clipped.value;
121
+ isTruncated = isTruncated || clipped.isTruncated;
122
+ }
123
+
124
+ if (payload.toolResult !== undefined) {
125
+ const clipped: { value: string | undefined; isTruncated: boolean } =
126
+ this.clip(payload.toolResult, MAX_TOOL_RESULT_CHARS);
127
+ clamped.toolResult = clipped.value;
128
+ isTruncated = isTruncated || clipped.isTruncated;
129
+ }
130
+
131
+ if (payload.responseToolCalls) {
132
+ clamped.responseToolCalls = payload.responseToolCalls.map(
133
+ (toolCall: {
134
+ id?: string | undefined;
135
+ name: string;
136
+ arguments?: JSONObject | undefined;
137
+ }) => {
138
+ const clipped: {
139
+ value: JSONObject | undefined;
140
+ isTruncated: boolean;
141
+ } = this.clipArguments(toolCall.arguments);
142
+
143
+ if (clipped.isTruncated) {
144
+ isTruncated = true;
145
+ }
146
+
147
+ return {
148
+ ...(toolCall.id ? { id: toolCall.id } : {}),
149
+ name: toolCall.name,
150
+ ...(clipped.value ? { arguments: clipped.value } : {}),
151
+ };
152
+ },
153
+ );
154
+ }
155
+
156
+ /*
157
+ * Sacrifice whole fields, largest-payoff first, RE-CHECKING after each —
158
+ * a single pass that drops only requestMessages cannot bound a payload
159
+ * whose bulk is elsewhere. A model that emits 30 write_file calls puts
160
+ * ~10k of arguments each into responseToolCalls, blowing the ceiling while
161
+ * the pass "succeeds" by deleting a two-character user message.
162
+ *
163
+ * Order is by debugging value, cheapest to lose first: the replayed
164
+ * request messages, then the tool arguments (their paths survive in the
165
+ * message), then the tool output. Each step reports what it dropped rather
166
+ * than leaving the reader to wonder.
167
+ */
168
+ const sacrifices: Array<() => void> = [
169
+ () => {
170
+ const messageCount: number = clamped.requestMessages?.length || 0;
171
+ clamped.requestMessages = [
172
+ {
173
+ role: "system",
174
+ content: `[${messageCount} request message(s) omitted — this step's transcript exceeded the ${MAX_PAYLOAD_CHARS} character cap.]`,
175
+ },
176
+ ];
177
+ },
178
+ () => {
179
+ if (!clamped.responseToolCalls) {
180
+ return;
181
+ }
182
+
183
+ clamped.responseToolCalls = clamped.responseToolCalls.map(
184
+ (toolCall: {
185
+ id?: string | undefined;
186
+ name: string;
187
+ arguments?: JSONObject | undefined;
188
+ }) => {
189
+ return {
190
+ ...(toolCall.id ? { id: toolCall.id } : {}),
191
+ name: toolCall.name,
192
+ arguments: {
193
+ _note: `Arguments omitted — this step's transcript exceeded the ${MAX_PAYLOAD_CHARS} character cap.`,
194
+ },
195
+ };
196
+ },
197
+ );
198
+ },
199
+ () => {
200
+ clamped.toolResult = `[Tool output omitted — this step's transcript exceeded the ${MAX_PAYLOAD_CHARS} character cap.]`;
201
+ },
202
+ () => {
203
+ clamped.responseContent = `[Model response omitted — this step's transcript exceeded the ${MAX_PAYLOAD_CHARS} character cap.]`;
204
+ },
205
+ /*
206
+ * Last resort: responseToolCalls has no bounded LENGTH, only bounded
207
+ * entries, so a response with enough calls stays over cap even with every
208
+ * argument already dropped. Collapse it to a count.
209
+ */
210
+ () => {
211
+ const toolCallCount: number = clamped.responseToolCalls?.length || 0;
212
+ delete clamped.responseToolCalls;
213
+ clamped.responseContent = `[${toolCallCount} tool call(s) and this step's content omitted — the transcript exceeded the ${MAX_PAYLOAD_CHARS} character cap.]`;
214
+ },
215
+ ];
216
+
217
+ for (const sacrifice of sacrifices) {
218
+ if (this.serializedLength(clamped) <= MAX_PAYLOAD_CHARS) {
219
+ break;
220
+ }
221
+
222
+ sacrifice();
223
+ isTruncated = true;
224
+ }
225
+
226
+ if (isTruncated) {
227
+ clamped.isTruncated = true;
228
+ }
229
+
230
+ return clamped;
231
+ }
232
+
233
+ private static serializedLength(payload: AIRunEventContentPayload): number {
234
+ try {
235
+ return JSON.stringify(payload).length;
236
+ } catch {
237
+ return Number.MAX_SAFE_INTEGER;
238
+ }
239
+ }
240
+ }
@@ -7,6 +7,7 @@ import { JSONObject } from "../../../../Types/JSON";
7
7
  import ObjectID from "../../../../Types/ObjectID";
8
8
  import AIChatMessageRole from "../../../../Types/AI/AIChatMessageRole";
9
9
  import AIChatMessageStatus from "../../../../Types/AI/AIChatMessageStatus";
10
+ import { AIChatPageContext } from "../../../../Types/AI/AIChatPageContext";
10
11
  import AIChatPermissionMode from "../../../../Types/AI/AIChatPermissionMode";
11
12
  import AIRunEventType from "../../../../Types/AI/AIRunEventType";
12
13
  import AIRunStatus from "../../../../Types/AI/AIRunStatus";
@@ -43,6 +44,13 @@ export interface ChatTurnRequest {
43
44
  llmProviderId?: ObjectID | undefined;
44
45
  // How much autonomy the agent has to run mutating tools in this conversation.
45
46
  permissionMode: AIChatPermissionMode;
47
+ /*
48
+ * What the user was looking at in the dashboard when they sent this message
49
+ * (already sanitized by the API). Folded into the system prompt so "this
50
+ * incident" resolves to the entity on screen. Per-turn: not persisted, and
51
+ * not needed on resume (the paused state already contains the built prompt).
52
+ */
53
+ pageContext?: AIChatPageContext | undefined;
46
54
  // The requesting user's real permission props, captured at request time.
47
55
  props: DatabaseCommonInteractionProps;
48
56
  }
@@ -891,6 +899,7 @@ export default class ChatAgentRunner {
891
899
  content: buildObservabilityChatSystemPrompt({
892
900
  currentTime: OneUptimeDate.getCurrentDate(),
893
901
  permissionMode: request.permissionMode,
902
+ pageContext: request.pageContext,
894
903
  }),
895
904
  },
896
905
  ];