@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
@@ -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,21 +8,27 @@ 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
15
  import BadDataException from "../../Types/Exception/BadDataException";
13
16
  import NotAuthorizedException from "../../Types/Exception/NotAuthorizedException";
14
17
  import SortOrder from "../../Types/BaseDatabase/SortOrder";
15
18
  import Select from "../Types/Database/Select";
16
19
  import { JSONObject } from "../../Types/JSON";
20
+ import Permission, { UserPermission } from "../../Types/Permission";
17
21
  import AIRunType from "../../Types/AI/AIRunType";
18
22
  import { CodeFixTaskTypeHelper } from "../../Types/AI/CodeFixTaskType";
19
23
  import AIRun from "../../Models/DatabaseModels/AIRun";
20
24
  import AIRunEvent from "../../Models/DatabaseModels/AIRunEvent";
25
+ import LlmLog from "../../Models/DatabaseModels/LlmLog";
21
26
  import Project from "../../Models/DatabaseModels/Project";
22
27
  import BaseModel from "../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
23
28
  import ProjectService from "../Services/ProjectService";
24
29
  import AIRunService from "../Services/AIRunService";
25
30
  import AIRunEventService from "../Services/AIRunEventService";
31
+ import LlmLogService from "../Services/LlmLogService";
26
32
 
27
33
  const MAX_EVENTS: number = 500;
28
34
 
@@ -56,6 +62,7 @@ const RUN_SELECT: Select<AIRun> = {
56
62
  triggeredByTelemetryExceptionId: true,
57
63
  totalTokens: true,
58
64
  codeFixTaskType: true,
65
+ taskNumber: true,
59
66
  attemptCount: true,
60
67
  llmCallCount: true,
61
68
  toolCallCount: true,
@@ -67,6 +74,50 @@ const RUN_SELECT: Select<AIRun> = {
67
74
  */
68
75
  };
69
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,
98
+ };
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
+
70
121
  /*
71
122
  * Serialize a run with codeFixTaskType normalized: a null column means
72
123
  * FixException (rows created before task recipes existed), and clients
@@ -163,6 +214,137 @@ export default class CodeFixRunAPI {
163
214
  }
164
215
  },
165
216
  );
217
+
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
+ */
235
+ this.router.post(
236
+ "/code-fix-run/logs/:runId",
237
+ UserMiddleware.getUserMiddleware,
238
+ async (
239
+ req: ExpressRequest,
240
+ res: ExpressResponse,
241
+ next: NextFunction,
242
+ ): Promise<void> => {
243
+ try {
244
+ const props: DatabaseCommonInteractionProps =
245
+ await this.getAccessCheckedProps(req);
246
+
247
+ const runIdString: string | undefined = req.params["runId"];
248
+
249
+ if (!runIdString) {
250
+ throw new BadDataException("runId is required.");
251
+ }
252
+
253
+ const runId: ObjectID = new ObjectID(runIdString);
254
+
255
+ const run: AIRun | null = await AIRunService.findOneBy({
256
+ query: {
257
+ _id: runId.toString(),
258
+ projectId: props.tenantId!,
259
+ runType: AIRunType.CodeFix,
260
+ },
261
+ select: RUN_SELECT,
262
+ props: { isRoot: true },
263
+ });
264
+
265
+ if (!run) {
266
+ throw new BadDataException(
267
+ "Code fix run not found (or it does not belong to this project).",
268
+ );
269
+ }
270
+
271
+ const canReadContent: boolean = this.callerCanReadRunContent(props);
272
+
273
+ const events: Array<AIRunEvent> = await AIRunEventService.findBy({
274
+ query: { aiRunId: run.id! },
275
+ select: LOG_EVENT_SELECT,
276
+ sort: { sequence: SortOrder.Ascending },
277
+ limit: MAX_EVENTS,
278
+ skip: 0,
279
+ props: { isRoot: true },
280
+ });
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
+
306
+ Response.sendJsonObjectResponse(req, res, {
307
+ run: runToJson(run),
308
+ events: eventsJson,
309
+ llmLogs: BaseModel.toJSONArray(llmLogs, LlmLog),
310
+ canReadContent: canReadContent,
311
+ });
312
+ return;
313
+ } catch (err) {
314
+ next(err);
315
+ return;
316
+ }
317
+ },
318
+ );
319
+ }
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
+ );
166
348
  }
167
349
 
168
350
  /*
@@ -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.",
@@ -0,0 +1,95 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * Two additions, both in service of debugging a code-fix run that went wrong.
5
+ *
6
+ * 1. AIRun.taskNumber + Project.aiRunCounter — the per-project "#42" handle,
7
+ * allocated exactly like Incident.incidentNumber (ProjectService holds the
8
+ * counter behind a Redis semaphore; see incrementAndGetAIRunCounter).
9
+ * 2. AIRunEvent.contentPayload — the verbatim prompt/response/tool transcript.
10
+ *
11
+ * The DDL here was produced by `npm run generate-postgres-migration`. Two
12
+ * unrelated OnCallDutyPolicyScheduleLayer default changes the generator also
13
+ * emitted (pre-existing whitespace drift between the entity defaults and the
14
+ * local schema) were dropped: they are not part of this change.
15
+ *
16
+ * The backfill rides along in the same migration rather than a follow-up so
17
+ * there is no window in which existing tasks are on-screen with a blank
18
+ * number.
19
+ */
20
+ export class AddAITaskNumberAndRunTranscript1784124919694
21
+ implements MigrationInterface
22
+ {
23
+ public name: string = "AddAITaskNumberAndRunTranscript1784124919694";
24
+
25
+ public async up(queryRunner: QueryRunner): Promise<void> {
26
+ await queryRunner.query(
27
+ `ALTER TABLE "Project" ADD "aiRunCounter" integer NOT NULL DEFAULT '0'`,
28
+ );
29
+ await queryRunner.query(`ALTER TABLE "AIRun" ADD "taskNumber" integer`);
30
+ await queryRunner.query(
31
+ `ALTER TABLE "AIRunEvent" ADD "contentPayload" jsonb`,
32
+ );
33
+ await queryRunner.query(
34
+ `CREATE INDEX "IDX_ede30aa8b14a167586bd555fa9" ON "AIRun" ("taskNumber") `,
35
+ );
36
+
37
+ /*
38
+ * Number the tasks that already exist, oldest first, per project — so the
39
+ * list does not read "#1, -, -, #2" the day this ships. Scoped to
40
+ * runType = 'CodeFix' because only code-fix runs are tasks; chat and
41
+ * investigation runs share this table and must stay null.
42
+ *
43
+ * Soft-deleted rows are numbered too. Not to prevent reuse (a skipped row
44
+ * would hold no number to reuse) but to keep the sequence a stable
45
+ * function of creation order: a run that is soft-deleted later must not
46
+ * renumber the runs after it, and an undeleted row must not collide with
47
+ * a number already handed out. The cost is gaps in what the list shows,
48
+ * which is the same thing incidents do.
49
+ */
50
+ await queryRunner.query(`
51
+ WITH numbered AS (
52
+ SELECT
53
+ "_id",
54
+ ROW_NUMBER() OVER (
55
+ PARTITION BY "projectId"
56
+ ORDER BY "createdAt" ASC, "_id" ASC
57
+ ) AS "rowNumber"
58
+ FROM "AIRun"
59
+ WHERE "runType" = 'CodeFix'
60
+ )
61
+ UPDATE "AIRun"
62
+ SET "taskNumber" = numbered."rowNumber"
63
+ FROM numbered
64
+ WHERE "AIRun"."_id" = numbered."_id"
65
+ `);
66
+
67
+ /*
68
+ * Advance each project's counter past what the backfill just handed out,
69
+ * so the next allocation continues the sequence instead of restarting at
70
+ * #1 and colliding. Projects with no code-fix runs keep the 0 default.
71
+ */
72
+ await queryRunner.query(`
73
+ UPDATE "Project"
74
+ SET "aiRunCounter" = "maxPerProject"."maxTaskNumber"
75
+ FROM (
76
+ SELECT "projectId", MAX("taskNumber") AS "maxTaskNumber"
77
+ FROM "AIRun"
78
+ WHERE "runType" = 'CodeFix' AND "taskNumber" IS NOT NULL
79
+ GROUP BY "projectId"
80
+ ) AS "maxPerProject"
81
+ WHERE "Project"."_id" = "maxPerProject"."projectId"
82
+ `);
83
+ }
84
+
85
+ public async down(queryRunner: QueryRunner): Promise<void> {
86
+ await queryRunner.query(
87
+ `DROP INDEX "public"."IDX_ede30aa8b14a167586bd555fa9"`,
88
+ );
89
+ await queryRunner.query(
90
+ `ALTER TABLE "AIRunEvent" DROP COLUMN "contentPayload"`,
91
+ );
92
+ await queryRunner.query(`ALTER TABLE "AIRun" DROP COLUMN "taskNumber"`);
93
+ await queryRunner.query(`ALTER TABLE "Project" DROP COLUMN "aiRunCounter"`);
94
+ }
95
+ }
@@ -0,0 +1,52 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * A pull request can now be proposed from an AI chat conversation, which has
5
+ * no AIAgent behind it (the AIAgent record belongs to the autonomous fix
6
+ * worker). aiAgentId becomes nullable so chat-authored PRs can be recorded at
7
+ * all — and recording them is what makes them count against the per-repository
8
+ * open-PR cap, which reads AIAgentTaskPullRequest rows.
9
+ *
10
+ * A null aiAgentId therefore means "proposed from chat". Existing rows all
11
+ * have an agent, so dropping NOT NULL needs no backfill and the down migration
12
+ * is safe until the first chat-authored PR exists.
13
+ */
14
+ export class AllowNullAiAgentOnPullRequest1784135099754
15
+ implements MigrationInterface
16
+ {
17
+ public name = "AllowNullAiAgentOnPullRequest1784135099754";
18
+
19
+ public async up(queryRunner: QueryRunner): Promise<void> {
20
+ /*
21
+ * The FK is dropped and re-added because Postgres will not alter a column's
22
+ * nullability while a foreign key references it. ON DELETE CASCADE is
23
+ * preserved exactly as it was.
24
+ */
25
+ await queryRunner.query(
26
+ `ALTER TABLE "AIAgentTaskPullRequest" DROP CONSTRAINT "FK_b1e01ecfe9eecfb555428471073"`,
27
+ );
28
+ await queryRunner.query(
29
+ `ALTER TABLE "AIAgentTaskPullRequest" ALTER COLUMN "aiAgentId" DROP NOT NULL`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "AIAgentTaskPullRequest" ADD CONSTRAINT "FK_b1e01ecfe9eecfb555428471073" FOREIGN KEY ("aiAgentId") REFERENCES "AIAgent"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
33
+ );
34
+ }
35
+
36
+ public async down(queryRunner: QueryRunner): Promise<void> {
37
+ /*
38
+ * Restoring NOT NULL fails if any chat-authored (null-agent) rows exist.
39
+ * That is deliberate: silently deleting or reassigning a customer's pull
40
+ * request records to satisfy a rollback would be worse than a loud failure.
41
+ */
42
+ await queryRunner.query(
43
+ `ALTER TABLE "AIAgentTaskPullRequest" DROP CONSTRAINT "FK_b1e01ecfe9eecfb555428471073"`,
44
+ );
45
+ await queryRunner.query(
46
+ `ALTER TABLE "AIAgentTaskPullRequest" ALTER COLUMN "aiAgentId" SET NOT NULL`,
47
+ );
48
+ await queryRunner.query(
49
+ `ALTER TABLE "AIAgentTaskPullRequest" ADD CONSTRAINT "FK_b1e01ecfe9eecfb555428471073" FOREIGN KEY ("aiAgentId") REFERENCES "AIAgent"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
50
+ );
51
+ }
52
+ }
@@ -0,0 +1,30 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ /*
4
+ * Status page owners can now stop AI agents from reading their status page over
5
+ * the public OneUptime MCP server. MCP access stays on unless it is turned off,
6
+ * so the column defaults to true.
7
+ *
8
+ * ADD COLUMN ... NOT NULL DEFAULT true populates every existing row with true in
9
+ * the same statement, so no backfill is needed and existing status pages keep
10
+ * working exactly as they do today. That is what makes the
11
+ * `enableMcpServer: true` predicate in StatusPageService.isMcpServerEnabled
12
+ * safe — there are no NULL rows for it to silently drop.
13
+ */
14
+ export class AddEnableMcpServerToStatusPage1784137457184
15
+ implements MigrationInterface
16
+ {
17
+ public name = "AddEnableMcpServerToStatusPage1784137457184";
18
+
19
+ public async up(queryRunner: QueryRunner): Promise<void> {
20
+ await queryRunner.query(
21
+ `ALTER TABLE "StatusPage" ADD "enableMcpServer" boolean NOT NULL DEFAULT true`,
22
+ );
23
+ }
24
+
25
+ public async down(queryRunner: QueryRunner): Promise<void> {
26
+ await queryRunner.query(
27
+ `ALTER TABLE "StatusPage" DROP COLUMN "enableMcpServer"`,
28
+ );
29
+ }
30
+ }
@@ -0,0 +1,49 @@
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan1784200000000
4
+ implements MigrationInterface
5
+ {
6
+ public name = "AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan1784200000000";
7
+
8
+ public async up(queryRunner: QueryRunner): Promise<void> {
9
+ await queryRunner.query(
10
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3SecurityLevel" character varying(100)`,
11
+ );
12
+ await queryRunner.query(
13
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3Username" character varying(100)`,
14
+ );
15
+ await queryRunner.query(
16
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3AuthProtocol" character varying(100)`,
17
+ );
18
+ await queryRunner.query(
19
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3AuthKey" text`,
20
+ );
21
+ await queryRunner.query(
22
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3PrivProtocol" character varying(100)`,
23
+ );
24
+ await queryRunner.query(
25
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" ADD "snmpV3PrivKey" text`,
26
+ );
27
+ }
28
+
29
+ public async down(queryRunner: QueryRunner): Promise<void> {
30
+ await queryRunner.query(
31
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3PrivKey"`,
32
+ );
33
+ await queryRunner.query(
34
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3PrivProtocol"`,
35
+ );
36
+ await queryRunner.query(
37
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3AuthKey"`,
38
+ );
39
+ await queryRunner.query(
40
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3AuthProtocol"`,
41
+ );
42
+ await queryRunner.query(
43
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3Username"`,
44
+ );
45
+ await queryRunner.query(
46
+ `ALTER TABLE "NetworkDeviceDiscoveryScan" DROP COLUMN "snmpV3SecurityLevel"`,
47
+ );
48
+ }
49
+ }
@@ -451,6 +451,10 @@ import { RenameSentinelToAI1784030612266 } from "./1784030612266-RenameSentinelT
451
451
  import { MigrationName1784033837629 } from "./1784033837629-MigrationName";
452
452
  import { AddCreatedByUserToUser1784048917994 } from "./1784048917994-AddCreatedByUserToUser";
453
453
  import { BackfillCodeFixTaskType1784105912819 } from "./1784105912819-BackfillCodeFixTaskType";
454
+ import { AddAITaskNumberAndRunTranscript1784124919694 } from "./1784124919694-AddAITaskNumberAndRunTranscript";
455
+ import { AllowNullAiAgentOnPullRequest1784135099754 } from "./1784135099754-AllowNullAiAgentOnPullRequest";
456
+ import { AddEnableMcpServerToStatusPage1784137457184 } from "./1784137457184-AddEnableMcpServerToStatusPage";
457
+ import { AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan1784200000000 } from "./1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan";
454
458
 
455
459
  export default [
456
460
  InitialMigration,
@@ -906,4 +910,8 @@ export default [
906
910
  MigrationName1784033837629,
907
911
  AddCreatedByUserToUser1784048917994,
908
912
  BackfillCodeFixTaskType1784105912819,
913
+ AddAITaskNumberAndRunTranscript1784124919694,
914
+ AllowNullAiAgentOnPullRequest1784135099754,
915
+ AddEnableMcpServerToStatusPage1784137457184,
916
+ AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan1784200000000,
909
917
  ];
@@ -2,13 +2,17 @@ import PositiveNumber from "../../Types/PositiveNumber";
2
2
  import ObjectID from "../../Types/ObjectID";
3
3
  import { JSONObject } from "../../Types/JSON";
4
4
  import AIRunEventType from "../../Types/AI/AIRunEventType";
5
- import { AIRunEventResultSummary } from "../../Types/AI/AIChatTypes";
5
+ import {
6
+ AIRunEventContentPayload,
7
+ AIRunEventResultSummary,
8
+ } from "../../Types/AI/AIChatTypes";
6
9
  import CountBy from "../Types/Database/CountBy";
7
10
  import FindBy from "../Types/Database/FindBy";
8
11
  import { OnFind } from "../Types/Database/Hooks";
9
12
  import DatabaseService from "./DatabaseService";
10
13
  import Model from "../../Models/DatabaseModels/AIRunEvent";
11
14
  import { pinQueryToRequestingUser } from "../Utils/AI/AIChatPrivacyFilter";
15
+ import AIRunTranscript from "../Utils/AI/AIRunTranscript";
12
16
  import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
13
17
  import logger from "../Utils/Logger";
14
18
 
@@ -36,6 +40,7 @@ export class Service extends DatabaseService<Model> {
36
40
  toolArguments?: JSONObject | undefined;
37
41
  resultSummary?: AIRunEventResultSummary | undefined;
38
42
  citationId?: string | undefined;
43
+ contentPayload?: AIRunEventContentPayload | undefined;
39
44
  }): Promise<void> {
40
45
  try {
41
46
  const sequence: number = (
@@ -63,6 +68,15 @@ export class Service extends DatabaseService<Model> {
63
68
  if (data.citationId) {
64
69
  event.citationId = data.citationId;
65
70
  }
71
+ /*
72
+ * Capped here rather than at the call sites, so no caller can write an
73
+ * unbounded transcript row by forgetting to clamp.
74
+ */
75
+ if (data.contentPayload) {
76
+ event.contentPayload = AIRunTranscript.clampPayload(
77
+ data.contentPayload,
78
+ );
79
+ }
66
80
 
67
81
  await this.create({
68
82
  data: event,