@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
@@ -196,6 +196,7 @@ export default class AIAgentTaskAPI {
196
196
  const validStatuses: Array<AIAgentTaskStatus> = [
197
197
  AIAgentTaskStatus.InProgress,
198
198
  AIAgentTaskStatus.Completed,
199
+ AIAgentTaskStatus.NoFixFound,
199
200
  AIAgentTaskStatus.Error,
200
201
  ];
201
202
 
@@ -249,16 +250,35 @@ export default class AIAgentTaskAPI {
249
250
  },
250
251
  });
251
252
  } else {
252
- const toStatus: AIRunStatus =
253
- status === AIAgentTaskStatus.Completed
254
- ? AIRunStatus.Completed
255
- : AIRunStatus.Error;
253
+ /*
254
+ * Agents that predate NoFixFound report a fruitless run as Error,
255
+ * so an Error report still maps straight through — those runs keep
256
+ * looking exactly as they do today until the agent is upgraded.
257
+ */
258
+ let toStatus: AIRunStatus = AIRunStatus.Error;
259
+
260
+ if (status === AIAgentTaskStatus.Completed) {
261
+ toStatus = AIRunStatus.Completed;
262
+ } else if (status === AIAgentTaskStatus.NoFixFound) {
263
+ toStatus = AIRunStatus.NoFixFound;
264
+ }
265
+
266
+ /*
267
+ * Both terminal outcomes that carry a reason store it in
268
+ * errorMessage — the column is the run's only message field, and
269
+ * every reader (the detail page, the exception page's
270
+ * statusMessage) already renders it.
271
+ */
272
+ const shouldStoreMessage: boolean =
273
+ Boolean(statusMessage) &&
274
+ (toStatus === AIRunStatus.Error ||
275
+ toStatus === AIRunStatus.NoFixFound);
256
276
 
257
277
  /*
258
- * CAS Running -> Completed/Error. Losing the race (0 rows) means
259
- * another actor already finalized the run — e.g. the sweeper
260
- * marked it Error after a heartbeat gap — and that outcome wins;
261
- * we do not clobber it or write a duplicate terminal event.
278
+ * CAS Running -> Completed/NoFixFound/Error. Losing the race (0
279
+ * rows) means another actor already finalized the run — e.g. the
280
+ * sweeper marked it Error after a heartbeat gap — and that outcome
281
+ * wins; we do not clobber it or write a duplicate terminal event.
262
282
  */
263
283
  const transitionedCount: number =
264
284
  await AIRunService.attemptStatusTransition({
@@ -268,7 +288,7 @@ export default class AIAgentTaskAPI {
268
288
  status: toStatus,
269
289
  completedAt: OneUptimeDate.getCurrentDate(),
270
290
  lastHeartbeatAt: OneUptimeDate.getCurrentDate(),
271
- ...(toStatus === AIRunStatus.Error && statusMessage
291
+ ...(shouldStoreMessage
272
292
  ? { errorMessage: statusMessage }
273
293
  : {}),
274
294
  },
@@ -278,10 +298,16 @@ export default class AIAgentTaskAPI {
278
298
  await AIRunEventService.appendEventToRun({
279
299
  projectId: existingRun.projectId,
280
300
  aiRunId: runId,
301
+ /*
302
+ * Only a genuine failure closes the event trail with
303
+ * RunFailed. A NoFixFound run ran to completion and reported
304
+ * a conclusion, so it closes with RunCompleted and carries
305
+ * the "no fix" reason in resultSummary.
306
+ */
281
307
  eventType:
282
- toStatus === AIRunStatus.Completed
283
- ? AIRunEventType.RunCompleted
284
- : AIRunEventType.RunFailed,
308
+ toStatus === AIRunStatus.Error
309
+ ? AIRunEventType.RunFailed
310
+ : AIRunEventType.RunCompleted,
285
311
  ...(statusMessage
286
312
  ? { resultSummary: { message: statusMessage } }
287
313
  : {}),
@@ -120,6 +120,31 @@ export default class AIAgentTaskLogAPI {
120
120
  );
121
121
  }
122
122
 
123
+ /*
124
+ * Optional verbatim tool detail (see TaskLogger.toolCall): the
125
+ * arguments as executed and the full output the model saw, recorded
126
+ * on the run's transcript for the Logs page. Absent it — an older
127
+ * agent — behaviour is exactly as before.
128
+ *
129
+ * The event type stays ProgressLog even when the detail is present.
130
+ * That is deliberate, not laziness: the Overview's ChatActivityFeed
131
+ * renders a ProgressLog by printing resultSummary.message, but
132
+ * renders a ToolCallCompleted by trying to COMPLETE a matching
133
+ * running step from an earlier ToolCallStarted — an event the
134
+ * code-fix path never emits. Typing these as ToolCallCompleted
135
+ * therefore matched nothing and silently erased every tool line from
136
+ * the Overview feed. The payload rides on the same event instead.
137
+ */
138
+ const toolName: string | undefined = data["toolName"] as
139
+ | string
140
+ | undefined;
141
+ const toolArguments: JSONObject | undefined = data[
142
+ "toolArguments"
143
+ ] as JSONObject | undefined;
144
+ const toolResult: string | undefined = data["toolResult"] as
145
+ | string
146
+ | undefined;
147
+
123
148
  await AIRunEventService.appendEventToRun({
124
149
  projectId: existingRun.projectId,
125
150
  aiRunId: runId,
@@ -128,6 +153,30 @@ export default class AIAgentTaskLogAPI {
128
153
  message: message,
129
154
  severity: severity,
130
155
  },
156
+ ...(toolName ? { toolName: toolName } : {}),
157
+ /*
158
+ * toolArguments (the column) is deliberately NOT written: its read
159
+ * ACL is project-member-wide, and a write_file call's arguments are
160
+ * the file's full new content. The same arguments go onto
161
+ * contentPayload below, whose read ACL is empty and which only the
162
+ * owner/admin-gated logs endpoint returns.
163
+ */
164
+ ...(toolName
165
+ ? {
166
+ contentPayload: {
167
+ ...(toolResult !== undefined
168
+ ? { toolResult: toolResult }
169
+ : {}),
170
+ ...(toolArguments
171
+ ? {
172
+ responseToolCalls: [
173
+ { name: toolName, arguments: toolArguments },
174
+ ],
175
+ }
176
+ : {}),
177
+ },
178
+ }
179
+ : {}),
131
180
  });
132
181
 
133
182
  /* A progress report proves the agent is alive — refresh the heartbeat. */
@@ -21,6 +21,10 @@ import SubscriptionPlan, {
21
21
  import { IsBillingEnabled, getAllEnvVars } from "../EnvironmentConfig";
22
22
  import AIChatMessageRole from "../../Types/AI/AIChatMessageRole";
23
23
  import AIChatMessageStatus from "../../Types/AI/AIChatMessageStatus";
24
+ import {
25
+ AIChatPageContext,
26
+ AIChatPageContextHelper,
27
+ } from "../../Types/AI/AIChatPageContext";
24
28
  import AIChatPermissionMode, {
25
29
  AIChatPermissionModeHelper,
26
30
  } from "../../Types/AI/AIChatPermissionMode";
@@ -143,6 +147,16 @@ router.post(
143
147
  ? (req.body["permissionMode"] as AIChatPermissionMode)
144
148
  : undefined;
145
149
 
150
+ /*
151
+ * Optional page context — what the user was looking at when they asked
152
+ * (an incident, a monitor, the logs explorer, …). It is a hint for the
153
+ * system prompt, so an invalid payload is dropped, never an error.
154
+ */
155
+ const pageContext: AIChatPageContext | undefined =
156
+ AIChatPageContextHelper.sanitize(
157
+ req.body["pageContext"] as JSONObject | undefined,
158
+ );
159
+
146
160
  // Plan gate: custom endpoints get no automatic billing check.
147
161
  if (
148
162
  IsBillingEnabled &&
@@ -435,6 +449,7 @@ router.post(
435
449
  aiRunId: createdRun.id!,
436
450
  llmProviderId: effectiveLlmProviderId,
437
451
  permissionMode: effectivePermissionMode,
452
+ pageContext: pageContext,
438
453
  props: props,
439
454
  }).catch((error: Error) => {
440
455
  logger.error(`AI chat turn crashed: ${error.message}`);
@@ -498,6 +513,7 @@ router.post(
498
513
  return {
499
514
  id: provider.id?.toString(),
500
515
  name: provider.name,
516
+ description: provider.description || null,
501
517
  llmType: provider.llmType?.toString() || null,
502
518
  modelName: provider.modelName || null,
503
519
  isDefault: provider.isDefault || false,
@@ -0,0 +1,84 @@
1
+ import Express, {
2
+ ExpressRequest,
3
+ ExpressResponse,
4
+ ExpressRouter,
5
+ NextFunction,
6
+ } from "../Utils/Express";
7
+ import Response from "../Utils/Response";
8
+ import UserMiddleware from "../Middleware/UserAuthorization";
9
+ import CommonAPI from "./CommonAPI";
10
+ import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
11
+ import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
12
+ import BadDataException from "../../Types/Exception/BadDataException";
13
+ import { JSONArray } from "../../Types/JSON";
14
+ import { AIFixReadiness } from "../../Types/AI/AIFixReadiness";
15
+ import CodeFixReadiness from "../Utils/AI/CodeFix/CodeFixReadiness";
16
+
17
+ /*
18
+ * Project-wide "can AI actually do anything here?" — the gates a CodeFix run
19
+ * passes through, answered for the whole project rather than one exception.
20
+ * The AI Tasks page renders this instead of a static prerequisites banner, so
21
+ * what the user reads reflects what the server would actually decide.
22
+ *
23
+ * The per-exception equivalent lives at
24
+ * GET /telemetry-exception/ai-fix-readiness/:id; both share the checks in
25
+ * CodeFixReadiness, so they cannot drift apart.
26
+ */
27
+ const router: ExpressRouter = Express.getRouter();
28
+
29
+ router.post(
30
+ "/ai-readiness/code-fix",
31
+ UserMiddleware.getUserMiddleware,
32
+ async (
33
+ req: ExpressRequest,
34
+ res: ExpressResponse,
35
+ next: NextFunction,
36
+ ): Promise<void> => {
37
+ try {
38
+ const props: DatabaseCommonInteractionProps =
39
+ await CommonAPI.getDatabaseCommonInteractionProps(req);
40
+
41
+ /*
42
+ * Authorize explicitly. getUserMiddleware admits anonymous callers
43
+ * (it sets userType = Public and calls next()), and tenantId is read
44
+ * straight off the caller-supplied `tenantid` header before any auth
45
+ * runs — so neither is evidence of anything on its own. The readiness
46
+ * checks below all query with `isRoot: true`, which means there is no
47
+ * RBAC layer underneath to catch an unauthorized caller: this handler
48
+ * is the only gate. The per-exception sibling gets away without this
49
+ * because it loads the exception with the caller's props first, and
50
+ * derives the project from that authorized row.
51
+ */
52
+ if (!props.userId) {
53
+ throw new NotAuthorizedException(
54
+ "AI readiness requires a logged-in user session.",
55
+ );
56
+ }
57
+
58
+ if (!props.tenantId) {
59
+ throw new BadDataException("Project ID is required (tenantid header).");
60
+ }
61
+
62
+ // Being logged in somewhere is not access to THIS project.
63
+ if (!props.userTenantAccessPermission?.[props.tenantId.toString()]) {
64
+ throw new NotAuthorizedException(
65
+ "You do not have access to this project.",
66
+ );
67
+ }
68
+
69
+ const readiness: AIFixReadiness =
70
+ await CodeFixReadiness.getProjectReadiness({
71
+ projectId: props.tenantId,
72
+ });
73
+
74
+ return Response.sendJsonObjectResponse(req, res, {
75
+ ready: readiness.ready,
76
+ checks: readiness.checks as unknown as JSONArray,
77
+ });
78
+ } catch (err) {
79
+ next(err);
80
+ }
81
+ },
82
+ );
83
+
84
+ export default router;
@@ -8,37 +8,48 @@ import Express, {
8
8
  } from "../Utils/Express";
9
9
  import Response from "../Utils/Response";
10
10
  import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
11
+ import DatabaseCommonInteractionPropsUtil, {
12
+ PermissionType,
13
+ } from "../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
11
14
  import ObjectID from "../../Types/ObjectID";
12
- import PositiveNumber from "../../Types/PositiveNumber";
13
15
  import BadDataException from "../../Types/Exception/BadDataException";
14
16
  import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
15
17
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
16
- import Query from "../../Types/BaseDatabase/Query";
17
18
  import Select from "../Types/Database/Select";
18
19
  import { JSONObject } from "../../Types/JSON";
20
+ import Permission, { UserPermission } from "../../Types/Permission";
19
21
  import AIRunType from "../../Types/AI/AIRunType";
20
22
  import { CodeFixTaskTypeHelper } from "../../Types/AI/CodeFixTaskType";
21
23
  import AIRun from "../../Models/DatabaseModels/AIRun";
22
24
  import AIRunEvent from "../../Models/DatabaseModels/AIRunEvent";
25
+ import LlmLog from "../../Models/DatabaseModels/LlmLog";
23
26
  import Project from "../../Models/DatabaseModels/Project";
24
27
  import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
25
28
  import ProjectService from "../Services/ProjectService";
26
29
  import AIRunService from "../Services/AIRunService";
27
30
  import AIRunEventService from "../Services/AIRunEventService";
31
+ import LlmLogService from "../Services/LlmLogService";
28
32
 
29
- const DEFAULT_LIMIT: number = 25;
30
- const MAX_LIMIT: number = 100;
31
33
  const MAX_EVENTS: number = 500;
32
34
 
33
35
  /*
34
- * Dedicated read endpoints for the dashboard's AI fix-task pages.
36
+ * The AI fix task detail page's event trail.
35
37
  *
36
- * Code-fix runs are system-authored AIRuns (runType CodeFix, userId = null)
37
- * and are therefore hidden by the per-user privacy pin on the generic
38
- * AIRun / AIRunEvent CRUD. So access is gated explicitly here — the same
39
- * pattern as AIInvestigationAPI: first confirm the caller can read the
40
- * parent object (the project) under THEIR permissions, then read the runs
41
- * and their events as root.
38
+ * The LIST page does NOT come through here it reads AIRuns through the
39
+ * standard /ai-run CRUD, which AIRunService opens up for code-fix runs via a
40
+ * forced `runType = CodeFix OR userId = <caller>` clause (see
41
+ * Utils/AI/AIRunPrivacyFilter).
42
+ *
43
+ * The events cannot follow. AIRunEvent has NO runType column to key that
44
+ * clause off, and "userId is null" does not mean "system-authored" — the
45
+ * investigation engine writes null-userId events too — so project-scoping
46
+ * AIRunEvent wholesale would expose other members' CHAT toolArguments, which
47
+ * carry user-typed prompt content. Events are therefore still reached only
48
+ * through a run this endpoint has already authorized as runType = CodeFix.
49
+ *
50
+ * Access is gated explicitly, the same pattern as AIInvestigationAPI: confirm
51
+ * the caller can read the parent object (the project) under THEIR permissions,
52
+ * then read the run and its events as root.
42
53
  */
43
54
 
44
55
  const RUN_SELECT: Select<AIRun> = {
@@ -51,8 +62,62 @@ const RUN_SELECT: Select<AIRun> = {
51
62
  triggeredByTelemetryExceptionId: true,
52
63
  totalTokens: true,
53
64
  codeFixTaskType: true,
65
+ taskNumber: true,
66
+ attemptCount: true,
67
+ llmCallCount: true,
68
+ toolCallCount: true,
69
+ /*
70
+ * Deliberately NOT selected: totalCostInUSDCents. This endpoint's gate is
71
+ * Project read, which admits roles beyond AIRun's own table ACL, so it must
72
+ * not return more than the detail page renders — and nothing there renders
73
+ * per-run spend.
74
+ */
75
+ };
76
+
77
+ /*
78
+ * The Logs page's event trail.
79
+ *
80
+ * toolArguments is deliberately NOT selected, even though this endpoint reads
81
+ * as root and could. A code-fix write_file call carries the file's full new
82
+ * content in its arguments, and that column's own read ACL stops at
83
+ * ProjectMember — narrower than this endpoint's Project-read gate, which also
84
+ * admits Viewers. Returning it would leak the same customer source code that
85
+ * contentPayload is stripped below to protect, to an even wider audience. The
86
+ * page never needed it: Logs.tsx renders tool arguments from
87
+ * contentPayload.responseToolCalls, the owner/admin-gated copy.
88
+ */
89
+ const LOG_EVENT_SELECT: Select<AIRunEvent> = {
90
+ _id: true,
91
+ sequence: true,
92
+ eventType: true,
93
+ toolName: true,
94
+ resultSummary: true,
95
+ citationId: true,
96
+ createdAt: true,
97
+ contentPayload: true,
54
98
  };
55
99
 
100
+ /*
101
+ * The per-LLM-call metering shown beside the transcript. Read under the
102
+ * CALLER's permissions (not root), so LlmLog's own table ACL decides who sees
103
+ * it rather than this endpoint's broader Project-read gate.
104
+ */
105
+ const LLM_LOG_SELECT: Select<LlmLog> = {
106
+ _id: true,
107
+ modelName: true,
108
+ llmProviderName: true,
109
+ status: true,
110
+ statusMessage: true,
111
+ totalTokens: true,
112
+ completionTokens: true,
113
+ durationMs: true,
114
+ requestStartedAt: true,
115
+ requestCompletedAt: true,
116
+ createdAt: true,
117
+ };
118
+
119
+ const MAX_LLM_LOGS: number = 500;
120
+
56
121
  /*
57
122
  * Serialize a run with codeFixTaskType normalized: a null column means
58
123
  * FixException (rows created before task recipes existed), and clients
@@ -81,9 +146,9 @@ export default class CodeFixRunAPI {
81
146
  }
82
147
 
83
148
  private initRoutes(): void {
84
- // List the project's code-fix runs, newest first.
149
+ // One run + its ordered glass-box event trail.
85
150
  this.router.post(
86
- "/code-fix-run/list",
151
+ "/code-fix-run/get/:runId",
87
152
  UserMiddleware.getUserMiddleware,
88
153
  async (
89
154
  req: ExpressRequest,
@@ -94,47 +159,53 @@ export default class CodeFixRunAPI {
94
159
  const props: DatabaseCommonInteractionProps =
95
160
  await this.getAccessCheckedProps(req);
96
161
 
97
- const body: JSONObject = (req.body as JSONObject) || {};
98
-
99
- let limit: number = DEFAULT_LIMIT;
100
-
101
- if (typeof body["limit"] === "number") {
102
- limit = body["limit"];
103
- }
104
-
105
- limit = Math.min(Math.max(Math.floor(limit), 1), MAX_LIMIT);
106
-
107
- let skip: number = 0;
162
+ const runIdString: string | undefined = req.params["runId"];
108
163
 
109
- if (typeof body["skip"] === "number") {
110
- skip = Math.max(Math.floor(body["skip"]), 0);
164
+ if (!runIdString) {
165
+ throw new BadDataException("runId is required.");
111
166
  }
112
167
 
113
- const query: Query<AIRun> = {
114
- projectId: props.tenantId!,
115
- runType: AIRunType.CodeFix,
116
- };
168
+ const runId: ObjectID = new ObjectID(runIdString);
117
169
 
118
- // Root read after the tenant access check above.
119
- const runs: Array<AIRun> = await AIRunService.findBy({
120
- query: query,
170
+ /*
171
+ * Root read, but pinned to the caller's (access-checked) project —
172
+ * a run id from another project resolves to "not found".
173
+ */
174
+ const run: AIRun | null = await AIRunService.findOneBy({
175
+ query: {
176
+ _id: runId.toString(),
177
+ projectId: props.tenantId!,
178
+ runType: AIRunType.CodeFix,
179
+ },
121
180
  select: RUN_SELECT,
122
- sort: { createdAt: SortOrder.Descending },
123
- limit: limit,
124
- skip: skip,
125
181
  props: { isRoot: true },
126
182
  });
127
183
 
128
- const count: PositiveNumber = await AIRunService.countBy({
129
- query: query,
184
+ if (!run) {
185
+ throw new BadDataException(
186
+ "Code fix run not found (or it does not belong to this project).",
187
+ );
188
+ }
189
+
190
+ const events: Array<AIRunEvent> = await AIRunEventService.findBy({
191
+ query: { aiRunId: run.id! },
192
+ select: {
193
+ _id: true,
194
+ sequence: true,
195
+ eventType: true,
196
+ toolName: true,
197
+ resultSummary: true,
198
+ createdAt: true,
199
+ },
200
+ sort: { sequence: SortOrder.Ascending },
201
+ limit: MAX_EVENTS,
202
+ skip: 0,
130
203
  props: { isRoot: true },
131
204
  });
132
205
 
133
206
  Response.sendJsonObjectResponse(req, res, {
134
- runs: runs.map((run: AIRun): JSONObject => {
135
- return runToJson(run);
136
- }),
137
- count: count.toNumber(),
207
+ run: runToJson(run),
208
+ events: BaseModel.toJSONArray(events, AIRunEvent),
138
209
  });
139
210
  return;
140
211
  } catch (err) {
@@ -144,9 +215,25 @@ export default class CodeFixRunAPI {
144
215
  },
145
216
  );
146
217
 
147
- // One run + its ordered glass-box event trail.
218
+ /*
219
+ * The Logs page: the same run, its FULL event trail, and the per-call LLM
220
+ * metering — everything recorded about a run, for debugging one that went
221
+ * wrong.
222
+ *
223
+ * Two things are gated more tightly than the trail itself:
224
+ *
225
+ * - contentPayload (the verbatim prompts, model replies and tool output)
226
+ * is returned only to ProjectOwner/ProjectAdmin. It embeds customer
227
+ * source code, which is why the column's own read ACL is empty and why
228
+ * LlmLog redacts the same content — this endpoint is the single
229
+ * deliberate door to it, and it is not open to every project member.
230
+ *
231
+ * - LlmLog rows are read under the CALLER's permissions rather than as
232
+ * root, so LlmLog's table ACL — not this endpoint's broader Project-read
233
+ * gate — decides who sees the metering.
234
+ */
148
235
  this.router.post(
149
- "/code-fix-run/get/:runId",
236
+ "/code-fix-run/logs/:runId",
150
237
  UserMiddleware.getUserMiddleware,
151
238
  async (
152
239
  req: ExpressRequest,
@@ -165,10 +252,6 @@ export default class CodeFixRunAPI {
165
252
 
166
253
  const runId: ObjectID = new ObjectID(runIdString);
167
254
 
168
- /*
169
- * Root read, but pinned to the caller's (access-checked) project —
170
- * a run id from another project resolves to "not found".
171
- */
172
255
  const run: AIRun | null = await AIRunService.findOneBy({
173
256
  query: {
174
257
  _id: runId.toString(),
@@ -185,25 +268,46 @@ export default class CodeFixRunAPI {
185
268
  );
186
269
  }
187
270
 
271
+ const canReadContent: boolean = this.callerCanReadRunContent(props);
272
+
188
273
  const events: Array<AIRunEvent> = await AIRunEventService.findBy({
189
274
  query: { aiRunId: run.id! },
190
- select: {
191
- _id: true,
192
- sequence: true,
193
- eventType: true,
194
- toolName: true,
195
- resultSummary: true,
196
- createdAt: true,
197
- },
275
+ select: LOG_EVENT_SELECT,
198
276
  sort: { sequence: SortOrder.Ascending },
199
277
  limit: MAX_EVENTS,
200
278
  skip: 0,
201
279
  props: { isRoot: true },
202
280
  });
203
281
 
282
+ const eventsJson: Array<JSONObject> = BaseModel.toJSONArray(
283
+ events,
284
+ AIRunEvent,
285
+ );
286
+
287
+ if (!canReadContent) {
288
+ for (const eventJson of eventsJson) {
289
+ delete eventJson["contentPayload"];
290
+ }
291
+ }
292
+
293
+ /*
294
+ * Under the caller's own permissions on purpose — see the note
295
+ * above. A caller without LlmLog read simply gets no metering rows.
296
+ */
297
+ const llmLogs: Array<LlmLog> = await LlmLogService.findBy({
298
+ query: { aiRunId: runId, projectId: props.tenantId! },
299
+ select: LLM_LOG_SELECT,
300
+ sort: { createdAt: SortOrder.Ascending },
301
+ limit: MAX_LLM_LOGS,
302
+ skip: 0,
303
+ props: props,
304
+ });
305
+
204
306
  Response.sendJsonObjectResponse(req, res, {
205
307
  run: runToJson(run),
206
- events: BaseModel.toJSONArray(events, AIRunEvent),
308
+ events: eventsJson,
309
+ llmLogs: BaseModel.toJSONArray(llmLogs, LlmLog),
310
+ canReadContent: canReadContent,
207
311
  });
208
312
  return;
209
313
  } catch (err) {
@@ -214,6 +318,35 @@ export default class CodeFixRunAPI {
214
318
  );
215
319
  }
216
320
 
321
+ /*
322
+ * Whether the caller may see a run's verbatim LLM/tool content. Project
323
+ * owners and admins only: the content embeds customer source code, so the
324
+ * bar is higher than the Project-read gate that admits them to the page.
325
+ *
326
+ * Read through getUserPermissions(Allow) rather than off
327
+ * userTenantAccessPermission directly, because that array holds GRANTS AND
328
+ * DENIALS together, discriminated only by isBlockPermission. Mapping it raw
329
+ * counts a team's explicit "block ProjectAdmin" entry as a grant of
330
+ * ProjectAdmin — inverting the control, so the admin action that restricts a
331
+ * team would be what hands it the source code.
332
+ */
333
+ private callerCanReadRunContent(
334
+ props: DatabaseCommonInteractionProps,
335
+ ): boolean {
336
+ const permissions: Array<Permission> =
337
+ DatabaseCommonInteractionPropsUtil.getUserPermissions(
338
+ props,
339
+ PermissionType.Allow,
340
+ ).map((userPermission: UserPermission) => {
341
+ return userPermission.permission;
342
+ });
343
+
344
+ return (
345
+ permissions.includes(Permission.ProjectOwner) ||
346
+ permissions.includes(Permission.ProjectAdmin)
347
+ );
348
+ }
349
+
217
350
  /*
218
351
  * Requires a logged-in session and a tenant, then verifies project access
219
352
  * by reading the project under the USER's permissions (null when the user
@@ -4,11 +4,11 @@ import BadDataException from "../../Types/Exception/BadDataException";
4
4
  import ObjectID from "../../Types/ObjectID";
5
5
  import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
6
6
  import TelemetryExceptionService, {
7
- AIFixReadiness,
8
7
  DashboardServiceSummary,
9
8
  DashboardSummaryResult,
10
9
  Service as TelemetryExceptionServiceType,
11
10
  } from "../Services/TelemetryExceptionService";
11
+ import { AIFixReadiness } from "../../Types/AI/AIFixReadiness";
12
12
  import AIRunService from "../Services/AIRunService";
13
13
  import UserMiddleware from "../Middleware/UserAuthorization";
14
14
  import Response from "../Utils/Response";
@@ -51,6 +51,11 @@ const CODE_FIX_STATUS_TEXT: {
51
51
  description:
52
52
  "The AI agent finished. Review the pull request it opened for the proposed fix.",
53
53
  },
54
+ [AIRunStatus.NoFixFound]: {
55
+ title: "No Fix Found",
56
+ description:
57
+ "The AI agent reviewed the code and did not find a fix to propose, so it opened no pull request.",
58
+ },
54
59
  [AIRunStatus.Error]: {
55
60
  title: "Error",
56
61
  description: "The AI agent could not complete the fix.",