@oneuptime/common 14.0.9 → 14.0.10

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 (383) hide show
  1. package/Models/DatabaseModels/AutoRemediationSuggestion.ts +60 -0
  2. package/Models/DatabaseModels/CephCluster.ts +213 -0
  3. package/Models/DatabaseModels/DatabaseServer.ts +213 -0
  4. package/Models/DatabaseModels/DockerHost.ts +213 -0
  5. package/Models/DatabaseModels/DockerSwarmCluster.ts +213 -0
  6. package/Models/DatabaseModels/GlobalConfig.ts +1 -1
  7. package/Models/DatabaseModels/Host.ts +213 -0
  8. package/Models/DatabaseModels/Index.ts +2 -0
  9. package/Models/DatabaseModels/PodmanHost.ts +213 -0
  10. package/Models/DatabaseModels/ProxmoxCluster.ts +213 -0
  11. package/Models/DatabaseModels/ResourceAiAgent.ts +419 -0
  12. package/Models/DatabaseModels/RunnerJob.ts +144 -0
  13. package/Models/DatabaseModels/VMwareVCenter.ts +213 -0
  14. package/Server/API/AutoRemediationAPI.ts +392 -1
  15. package/Server/API/ResourceAiAccessAPI.ts +1669 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.ts +423 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
  18. package/Server/Infrastructure/Semaphore.ts +22 -0
  19. package/Server/Middleware/TelemetryIngest.ts +15 -5
  20. package/Server/Services/AnalyticsDatabaseService.ts +45 -1
  21. package/Server/Services/AutoRemediationRuleEngineService.ts +940 -112
  22. package/Server/Services/CephClusterService.ts +109 -1
  23. package/Server/Services/DatabaseServerService.ts +116 -1
  24. package/Server/Services/DockerHostService.ts +109 -1
  25. package/Server/Services/DockerSwarmClusterService.ts +113 -1
  26. package/Server/Services/HostService.ts +126 -4
  27. package/Server/Services/MetricRecordingRuleService.ts +47 -0
  28. package/Server/Services/PodmanHostService.ts +109 -1
  29. package/Server/Services/ProxmoxClusterService.ts +110 -1
  30. package/Server/Services/ResourceAiAccessService.ts +1439 -0
  31. package/Server/Services/ResourceAiAgentJobService.ts +202 -0
  32. package/Server/Services/ResourceAiAgentService.ts +2432 -0
  33. package/Server/Services/RunnerJobService.ts +611 -4
  34. package/Server/Services/TelemetryUsageBillingService.ts +28 -0
  35. package/Server/Services/TraceRecordingRuleService.ts +33 -0
  36. package/Server/Services/VMwareVCenterService.ts +110 -1
  37. package/Server/Utils/AI/Remediation/RemediationCommandTools.ts +1434 -32
  38. package/Server/Utils/AI/Remediation/RemediationExecutionRunner.ts +935 -21
  39. package/Server/Utils/AI/Remediation/RemediationPlanRunner.ts +25 -1
  40. package/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.ts +577 -0
  41. package/Server/Utils/AI/ResourceAccess/ResourceAccessContext.ts +271 -0
  42. package/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.ts +45 -0
  43. package/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.ts +964 -0
  44. package/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.ts +333 -0
  45. package/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.ts +873 -0
  46. package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +72 -2
  47. package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +72 -5
  48. package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +72 -5
  49. package/Server/Utils/AutoRemediation/CommandPlanExecutor.ts +396 -13
  50. package/Server/Utils/AutoRemediation/RemediationVerifier.ts +35 -2
  51. package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +9 -1
  52. package/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.ts +920 -0
  53. package/Server/Utils/SessionReplay/SessionReplayUsage.ts +84 -6
  54. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +35 -15
  55. package/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.ts +33 -15
  56. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +21 -1
  57. package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +298 -224
  58. package/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.ts +35 -15
  59. package/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.ts +389 -175
  60. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +366 -143
  61. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.ts +163 -0
  62. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.ts +396 -0
  63. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.ts +418 -0
  64. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.ts +152 -0
  65. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.ts +251 -0
  66. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.ts +214 -0
  67. package/Tests/App/Dashboard/ClusterAccessNotice.test.tsx +93 -0
  68. package/Tests/App/Dashboard/DatabaseDocumentationMarkdown.test.ts +18 -6
  69. package/Tests/App/Dashboard/InvestigationInfrastructureTools.test.tsx +506 -0
  70. package/Tests/App/Dashboard/RemediationSuggestionCardDescription.test.tsx +209 -0
  71. package/Tests/App/Dashboard/RemediationSuggestionCardResource.test.tsx +317 -0
  72. package/Tests/App/Dashboard/ResourceAiAccessSettingsUtil.test.ts +921 -0
  73. package/Tests/App/Dashboard/ResourceAiAgentInstall.test.ts +860 -0
  74. package/Tests/App/Dashboard/ResourceAiAgentPage.test.tsx +1721 -0
  75. package/Tests/App/Dashboard/ResourceAiAgentStatus.test.ts +1002 -0
  76. package/Tests/App/Dashboard/ResourceAiInsightsPage.test.tsx +931 -0
  77. package/Tests/App/Dashboard/ResourceAiNavigation.test.tsx +575 -0
  78. package/Tests/App/Dashboard/RunbookStepTypeMaps.test.ts +38 -7
  79. package/Tests/Models/DatabaseModels/DatabaseServerModels.test.ts +33 -1
  80. package/Tests/Models/DatabaseModels/ResourceAiAccessColumns.test.ts +738 -0
  81. package/Tests/Models/DatabaseModels/ResourceAiAgentModel.test.ts +842 -0
  82. package/Tests/Server/API/AutoRemediationApproveResourceRound.test.ts +835 -0
  83. package/Tests/Server/API/ResourceAiAccessAPI.test.ts +2730 -0
  84. package/Tests/Server/Infrastructure/Postgres/AddDatabaseServerTablesMigration.test.ts +60 -0
  85. package/Tests/Server/Infrastructure/Postgres/AddResourceAiAgentsMigration.test.ts +659 -0
  86. package/Tests/Server/Infrastructure/SemaphoreMutex.test.ts +31 -0
  87. package/Tests/Server/Middleware/TelemetryIngestBrowserKey.test.ts +63 -5
  88. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerPinnedKey.test.ts +103 -5
  89. package/Tests/Server/Middleware/TelemetryIngestKubernetesAgentRunnerRateLimit.test.ts +19 -1
  90. package/Tests/Server/Services/AutoRemediationResourceRuleEngine.test.ts +1279 -0
  91. package/Tests/Server/Services/DatabaseServerService.test.ts +55 -0
  92. package/Tests/Server/Services/GroupTelemetryUsageExcludeNames.test.ts +299 -0
  93. package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +44 -0
  94. package/Tests/Server/Services/MonitorProbeServiceIntervalScheduling.test.ts +45 -6
  95. package/Tests/Server/Services/RecordingRuleReservedMetricName.test.ts +171 -0
  96. package/Tests/Server/Services/ResourceAiAccessChangeAuthorization.test.ts +256 -0
  97. package/Tests/Server/Services/ResourceAiAccessService.test.ts +1373 -0
  98. package/Tests/Server/Services/ResourceAiAgentJobService.test.ts +375 -0
  99. package/Tests/Server/Services/ResourceAiAgentServiceHelpers.test.ts +1114 -0
  100. package/Tests/Server/Services/ResourceAiAgentServiceLifecycle.test.ts +1050 -0
  101. package/Tests/Server/Services/ResourceAiAgentServiceRegister.test.ts +2251 -0
  102. package/Tests/Server/Services/ResourceAiSettingsCreate.test.ts +373 -0
  103. package/Tests/Server/Services/ResourceAiSettingsPermission.test.ts +1042 -0
  104. package/Tests/Server/Services/ResourceServiceDeleteCleansUpAi.test.ts +319 -0
  105. package/Tests/Server/Services/RunnerJobEnqueueKubectl.test.ts +105 -0
  106. package/Tests/Server/Services/RunnerJobEnqueueResourceCommand.test.ts +986 -0
  107. package/Tests/Server/Services/RunnerJobResourceCommandLane.test.ts +211 -0
  108. package/Tests/Server/Services/RunnerJobResourceCommandTimeoutAndRedaction.test.ts +352 -0
  109. package/Tests/Server/Services/TelemetryUsageBillingSloExclusion.test.ts +218 -0
  110. package/Tests/Server/TestingUtils/Services/FakeRunnerJobCount.ts +145 -0
  111. package/Tests/Server/Utils/AI/InvestigationInfrastructureAccessWiring.test.ts +453 -0
  112. package/Tests/Server/Utils/AI/InvestigationInfrastructureReport.test.ts +721 -0
  113. package/Tests/Server/Utils/AI/RemediationCommandTools.test.ts +94 -1
  114. package/Tests/Server/Utils/AI/RemediationCommandToolsResource.test.ts +1530 -0
  115. package/Tests/Server/Utils/AI/RemediationExecutionRunnerResourceMode.test.ts +1249 -0
  116. package/Tests/Server/Utils/AI/RemediationPlanRunner.test.ts +117 -0
  117. package/Tests/Server/Utils/AI/RemediationResourceCopyParity.test.ts +463 -0
  118. package/Tests/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.test.ts +722 -0
  119. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAccessContext.test.ts +266 -0
  120. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.test.ts +1220 -0
  121. package/Tests/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.test.ts +569 -0
  122. package/Tests/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.test.ts +830 -0
  123. package/Tests/Server/Utils/AutoRemediation/CommandPlanExecutorResource.test.ts +717 -0
  124. package/Tests/Server/Utils/AutoRemediation/RemediationVerifierResourceFollowUp.test.ts +338 -0
  125. package/Tests/Server/Utils/Monitor/Criteria/SessionReplayBudgetTemplateCriteria.test.ts +422 -0
  126. package/Tests/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.test.ts +1647 -0
  127. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +143 -1
  128. package/Tests/Server/Utils/Telemetry/TelemetryIngestionKeyGuard.test.ts +33 -3
  129. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsAccountNotLinked.test.ts +1093 -0
  130. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.test.ts +1049 -0
  131. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.test.ts +1676 -0
  132. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCards.test.ts +2884 -0
  133. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.test.ts +2490 -0
  134. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmit.test.ts +2293 -0
  135. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateSubmitServerTimezone.test.ts +386 -0
  136. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.test.ts +1477 -0
  137. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.test.ts +1967 -0
  138. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsStripHtmlTags.test.ts +92 -0
  139. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsSubmittedFormRemoval.test.ts +1819 -0
  140. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.test.ts +1033 -0
  141. package/Tests/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezoneServerZone.test.ts +610 -0
  142. package/Tests/Server/Utils/Workspace/MicrosoftTeamsBotMessageHandling.test.ts +2610 -0
  143. package/Tests/Server/Utils/Workspace/MicrosoftTeamsCreateCommandsEndToEnd.test.ts +4672 -0
  144. package/Tests/Server/Utils/Workspace/WorkspaceCreateProjectReferences.test.ts +190 -65
  145. package/Tests/Types/AutoRemediation/AiRemediationCommandPlan.test.ts +10 -4
  146. package/Tests/Types/AutoRemediation/AiRemediationCommandPlanResourceCommand.test.ts +305 -0
  147. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +277 -13
  148. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +113 -0
  149. package/Tests/Types/Monitor/RumAlertTemplates.test.ts +773 -2
  150. package/Tests/Types/ResourceAiAgent/AiResourceType.test.ts +419 -0
  151. package/Tests/Types/ResourceAiAgent/ResourceAiAccess.test.ts +517 -0
  152. package/Tests/Types/Runbook/RunbookStepType.test.ts +147 -8
  153. package/Tests/UI/Rum/RecordingHealthDashboard.test.tsx +769 -1
  154. package/Tests/Utils/AiRemediation/Resource/CephCommandPolicy.test.ts +1653 -0
  155. package/Tests/Utils/AiRemediation/Resource/CephOutputRedaction.test.ts +204 -0
  156. package/Tests/Utils/AiRemediation/Resource/DatabaseCommandPolicy.test.ts +1077 -0
  157. package/Tests/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.test.ts +558 -0
  158. package/Tests/Utils/AiRemediation/Resource/DatabaseQueryRedactor.test.ts +834 -0
  159. package/Tests/Utils/AiRemediation/Resource/DockerCliGrammar.test.ts +747 -0
  160. package/Tests/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.test.ts +1259 -0
  161. package/Tests/Utils/AiRemediation/Resource/DockerOutputRedaction.test.ts +386 -0
  162. package/Tests/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.test.ts +782 -0
  163. package/Tests/Utils/AiRemediation/Resource/GovcCommandPolicy.test.ts +2259 -0
  164. package/Tests/Utils/AiRemediation/Resource/HostCommandPolicy.test.ts +2019 -0
  165. package/Tests/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.test.ts +2497 -0
  166. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicy.test.ts +1305 -0
  167. package/Tests/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.test.ts +585 -0
  168. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactor.test.ts +334 -0
  169. package/Tests/Utils/AiRemediation/Resource/ResourceOutputRedactorCopyParity.test.ts +98 -0
  170. package/Tests/Utils/AiRemediation/Resource/ResourcePolicyImportClosure.test.ts +391 -0
  171. package/Tests/Utils/AiRemediation/ResourceAiAgentPolicyCopyParity.test.ts +497 -0
  172. package/Tests/Utils/SessionReplay/SessionReplayBudgetMetricType.test.ts +383 -0
  173. package/Types/AI/ResourceAiAccessApi.ts +140 -0
  174. package/Types/AI/ResourceAiAccessPermissions.ts +126 -0
  175. package/Types/AutoRemediation/AiRemediationCommandPlan.ts +183 -2
  176. package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +55 -22
  177. package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +35 -3
  178. package/Types/Monitor/RumAlertTemplates.ts +351 -4
  179. package/Types/ResourceAiAgent/AiResourceType.ts +310 -0
  180. package/Types/ResourceAiAgent/ResourceAiAccess.ts +574 -0
  181. package/Types/Rum/SessionReplayBudgetMetricType.ts +47 -0
  182. package/Types/Runbook/RunbookStepType.ts +29 -0
  183. package/Types/Telemetry/TelemetryIngestSurface.ts +14 -2
  184. package/Utils/AI/InvestigationReport.ts +121 -15
  185. package/Utils/AiRemediation/Resource/CephCommandPolicy.ts +1936 -0
  186. package/Utils/AiRemediation/Resource/DatabaseCommandPolicy.ts +166 -0
  187. package/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.ts +1532 -0
  188. package/Utils/AiRemediation/Resource/DatabaseQueryRedactor.ts +1394 -0
  189. package/Utils/AiRemediation/Resource/DockerCliGrammar.ts +1839 -0
  190. package/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.ts +490 -0
  191. package/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.ts +687 -0
  192. package/Utils/AiRemediation/Resource/GovcCommandPolicy.ts +2096 -0
  193. package/Utils/AiRemediation/Resource/HostCommandPolicy.ts +2982 -0
  194. package/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.ts +1679 -0
  195. package/Utils/AiRemediation/Resource/ResourceCommandPolicy.ts +851 -0
  196. package/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.ts +593 -0
  197. package/Utils/AiRemediation/Resource/ResourceOutputRedactor.ts +2812 -0
  198. package/Utils/SessionReplay/SessionReplayBudgetMetricType.ts +184 -0
  199. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js +62 -0
  200. package/build/dist/Models/DatabaseModels/AutoRemediationSuggestion.js.map +1 -1
  201. package/build/dist/Models/DatabaseModels/CephCluster.js +219 -0
  202. package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
  203. package/build/dist/Models/DatabaseModels/DatabaseServer.js +219 -0
  204. package/build/dist/Models/DatabaseModels/DatabaseServer.js.map +1 -1
  205. package/build/dist/Models/DatabaseModels/DockerHost.js +219 -0
  206. package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
  207. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +219 -0
  208. package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
  209. package/build/dist/Models/DatabaseModels/GlobalConfig.js +1 -1
  210. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  211. package/build/dist/Models/DatabaseModels/Host.js +219 -0
  212. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  213. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  214. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  215. package/build/dist/Models/DatabaseModels/PodmanHost.js +219 -0
  216. package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
  217. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +219 -0
  218. package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
  219. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js +439 -0
  220. package/build/dist/Models/DatabaseModels/ResourceAiAgent.js.map +1 -0
  221. package/build/dist/Models/DatabaseModels/RunnerJob.js +145 -0
  222. package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
  223. package/build/dist/Models/DatabaseModels/VMwareVCenter.js +219 -0
  224. package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -1
  225. package/build/dist/Server/API/AutoRemediationAPI.js +231 -1
  226. package/build/dist/Server/API/AutoRemediationAPI.js.map +1 -1
  227. package/build/dist/Server/API/ResourceAiAccessAPI.js +1119 -0
  228. package/build/dist/Server/API/ResourceAiAccessAPI.js.map +1 -0
  229. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js +168 -0
  230. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1796300000000-AddResourceAiAgents.js.map +1 -0
  231. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
  232. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  233. package/build/dist/Server/Infrastructure/Semaphore.js +6 -0
  234. package/build/dist/Server/Infrastructure/Semaphore.js.map +1 -1
  235. package/build/dist/Server/Middleware/TelemetryIngest.js +12 -5
  236. package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
  237. package/build/dist/Server/Services/AnalyticsDatabaseService.js +26 -1
  238. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  239. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js +601 -3
  240. package/build/dist/Server/Services/AutoRemediationRuleEngineService.js.map +1 -1
  241. package/build/dist/Server/Services/CephClusterService.js +104 -0
  242. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  243. package/build/dist/Server/Services/DatabaseServerService.js +99 -0
  244. package/build/dist/Server/Services/DatabaseServerService.js.map +1 -1
  245. package/build/dist/Server/Services/DockerHostService.js +104 -0
  246. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  247. package/build/dist/Server/Services/DockerSwarmClusterService.js +104 -0
  248. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  249. package/build/dist/Server/Services/HostService.js +114 -2
  250. package/build/dist/Server/Services/HostService.js.map +1 -1
  251. package/build/dist/Server/Services/MetricRecordingRuleService.js +46 -0
  252. package/build/dist/Server/Services/MetricRecordingRuleService.js.map +1 -1
  253. package/build/dist/Server/Services/PodmanHostService.js +104 -0
  254. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  255. package/build/dist/Server/Services/ProxmoxClusterService.js +104 -0
  256. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  257. package/build/dist/Server/Services/ResourceAiAccessService.js +1028 -0
  258. package/build/dist/Server/Services/ResourceAiAccessService.js.map +1 -0
  259. package/build/dist/Server/Services/ResourceAiAgentJobService.js +173 -0
  260. package/build/dist/Server/Services/ResourceAiAgentJobService.js.map +1 -0
  261. package/build/dist/Server/Services/ResourceAiAgentService.js +1687 -0
  262. package/build/dist/Server/Services/ResourceAiAgentService.js.map +1 -0
  263. package/build/dist/Server/Services/RunnerJobService.js +344 -5
  264. package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
  265. package/build/dist/Server/Services/TelemetryUsageBillingService.js +26 -0
  266. package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
  267. package/build/dist/Server/Services/TraceRecordingRuleService.js +37 -0
  268. package/build/dist/Server/Services/TraceRecordingRuleService.js.map +1 -1
  269. package/build/dist/Server/Services/VMwareVCenterService.js +104 -0
  270. package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -1
  271. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js +1029 -30
  272. package/build/dist/Server/Utils/AI/Remediation/RemediationCommandTools.js.map +1 -1
  273. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js +633 -28
  274. package/build/dist/Server/Utils/AI/Remediation/RemediationExecutionRunner.js.map +1 -1
  275. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js +21 -1
  276. package/build/dist/Server/Utils/AI/Remediation/RemediationPlanRunner.js.map +1 -1
  277. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js +330 -0
  278. package/build/dist/Server/Utils/AI/ResourceAccess/InfrastructureInvestigationToolkit.js.map +1 -0
  279. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js +160 -0
  280. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessContext.js.map +1 -0
  281. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js +33 -0
  282. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAccessToolNames.js.map +1 -0
  283. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js +640 -0
  284. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiAccessSettings.js.map +1 -0
  285. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js +226 -0
  286. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceAiDeleteCleanup.js.map +1 -0
  287. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js +600 -0
  288. package/build/dist/Server/Utils/AI/ResourceAccess/ResourceCommandJobRunner.js.map +1 -0
  289. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +50 -4
  290. package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
  291. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +55 -4
  292. package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
  293. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +55 -4
  294. package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
  295. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js +290 -13
  296. package/build/dist/Server/Utils/AutoRemediation/CommandPlanExecutor.js.map +1 -1
  297. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js +29 -1
  298. package/build/dist/Server/Utils/AutoRemediation/RemediationVerifier.js.map +1 -1
  299. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +9 -1
  300. package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
  301. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js +627 -0
  302. package/build/dist/Server/Utils/SessionReplay/SessionReplayBudgetMetrics.js.map +1 -0
  303. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js +60 -5
  304. package/build/dist/Server/Utils/SessionReplay/SessionReplayUsage.js.map +1 -1
  305. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +19 -15
  306. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
  307. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js +19 -15
  308. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/AlertEpisode.js.map +1 -1
  309. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +17 -1
  310. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
  311. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +183 -209
  312. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
  313. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js +19 -15
  314. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/IncidentEpisode.js.map +1 -1
  315. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js +233 -167
  316. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/ScheduledMaintenance.js.map +1 -1
  317. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +263 -110
  318. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  319. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js +129 -0
  320. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsActivityDeduplicator.js.map +1 -0
  321. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js +266 -0
  322. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCardChoices.js.map +1 -0
  323. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js +258 -0
  324. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsCreateCommands.js.map +1 -0
  325. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js +104 -0
  326. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsMessageSize.js.map +1 -0
  327. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js +183 -0
  328. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsReplies.js.map +1 -0
  329. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js +123 -0
  330. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeamsTimezone.js.map +1 -0
  331. package/build/dist/Types/AI/ResourceAiAccessApi.js +30 -0
  332. package/build/dist/Types/AI/ResourceAiAccessApi.js.map +1 -0
  333. package/build/dist/Types/AI/ResourceAiAccessPermissions.js +98 -0
  334. package/build/dist/Types/AI/ResourceAiAccessPermissions.js.map +1 -0
  335. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js +116 -29
  336. package/build/dist/Types/AutoRemediation/AiRemediationCommandPlan.js.map +1 -1
  337. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +47 -21
  338. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
  339. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +5 -0
  340. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
  341. package/build/dist/Types/Monitor/RumAlertTemplates.js +199 -3
  342. package/build/dist/Types/Monitor/RumAlertTemplates.js.map +1 -1
  343. package/build/dist/Types/ResourceAiAgent/AiResourceType.js +242 -0
  344. package/build/dist/Types/ResourceAiAgent/AiResourceType.js.map +1 -0
  345. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js +275 -0
  346. package/build/dist/Types/ResourceAiAgent/ResourceAiAccess.js.map +1 -0
  347. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js +45 -0
  348. package/build/dist/Types/Rum/SessionReplayBudgetMetricType.js.map +1 -0
  349. package/build/dist/Types/Runbook/RunbookStepType.js +27 -0
  350. package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
  351. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js +13 -2
  352. package/build/dist/Types/Telemetry/TelemetryIngestSurface.js.map +1 -1
  353. package/build/dist/Utils/AI/InvestigationReport.js +71 -11
  354. package/build/dist/Utils/AI/InvestigationReport.js.map +1 -1
  355. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js +1462 -0
  356. package/build/dist/Utils/AiRemediation/Resource/CephCommandPolicy.js.map +1 -0
  357. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js +122 -0
  358. package/build/dist/Utils/AiRemediation/Resource/DatabaseCommandPolicy.js.map +1 -0
  359. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js +1139 -0
  360. package/build/dist/Utils/AiRemediation/Resource/DatabaseDiagnosticCatalog.js.map +1 -0
  361. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js +1060 -0
  362. package/build/dist/Utils/AiRemediation/Resource/DatabaseQueryRedactor.js.map +1 -0
  363. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js +1301 -0
  364. package/build/dist/Utils/AiRemediation/Resource/DockerCliGrammar.js.map +1 -0
  365. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js +370 -0
  366. package/build/dist/Utils/AiRemediation/Resource/DockerEngineCommandPolicy.js.map +1 -0
  367. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js +498 -0
  368. package/build/dist/Utils/AiRemediation/Resource/DockerSwarmCommandPolicy.js.map +1 -0
  369. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js +1565 -0
  370. package/build/dist/Utils/AiRemediation/Resource/GovcCommandPolicy.js.map +1 -0
  371. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js +2206 -0
  372. package/build/dist/Utils/AiRemediation/Resource/HostCommandPolicy.js.map +1 -0
  373. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js +1129 -0
  374. package/build/dist/Utils/AiRemediation/Resource/ProxmoxCommandPolicy.js.map +1 -0
  375. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js +565 -0
  376. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicy.js.map +1 -0
  377. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js +408 -0
  378. package/build/dist/Utils/AiRemediation/Resource/ResourceCommandPolicyCore.js.map +1 -0
  379. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js +1910 -0
  380. package/build/dist/Utils/AiRemediation/Resource/ResourceOutputRedactor.js.map +1 -0
  381. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js +160 -0
  382. package/build/dist/Utils/SessionReplay/SessionReplayBudgetMetricType.js.map +1 -0
  383. package/package.json +1 -1
@@ -0,0 +1,1002 @@
1
+ import { describe, expect, test } from "@jest/globals";
2
+ import {
3
+ RESOURCE_AI_ACCESS_INSIGHTS_ROUTE,
4
+ RESOURCE_AI_ACCESS_RESET_AGENT_ROUTE,
5
+ RESOURCE_AI_ACCESS_STATUS_ROUTE,
6
+ RESOURCE_AI_ACCESS_TEST_ROUTE,
7
+ RESOURCE_AI_AGENT_PAGE_TITLE,
8
+ RESOURCE_AI_AGENT_STATUS_POLL_INTERVAL_MS,
9
+ RESOURCE_AI_CHOICE_GAP_CODES,
10
+ RESOURCE_AI_REFUSED_REGISTRATION_WARNING_WINDOW_MS,
11
+ ResourceAccessTestResult,
12
+ describeResourceAiAgentWriteAccess,
13
+ formatResourceToolVersion,
14
+ getResourceAiAccessRequestBody,
15
+ getResourceAiAgentCardCommand,
16
+ getResourceAiAgentCardState,
17
+ getResourceAiAgentGapAction,
18
+ getResourceAiAgentGoneText,
19
+ getResourceAiAgentMetaParts,
20
+ getResourceAiAgentNotInstalledText,
21
+ getResourceAiAgentOfflineReason,
22
+ getResourceAiAgentPageHint,
23
+ getResourceAiAgentPageSubtitle,
24
+ getResourceAiAgentReadyText,
25
+ getResourceAiAgentSignedOffText,
26
+ getResourceAiAgentSilentText,
27
+ getResourceAiAgentStateSentence,
28
+ getResourceAiAgentStatusPill,
29
+ getResourceAiAttentionGaps,
30
+ getResourceAiRefusedRegistrationWarning,
31
+ isResourceUnreachable,
32
+ parseResourceAccessTestResult,
33
+ parseResourceAiAccessStatus,
34
+ shouldShowResourceWriteAccessCommands,
35
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiAgentStatus";
36
+ import {
37
+ RESOURCE_AI_AGENT_DESCRIPTORS,
38
+ ResourceAiAgentDescriptor,
39
+ getResourceAiAgentDescriptor,
40
+ } from "../../../../App/FeatureSet/Dashboard/src/Components/ResourceAiAgent/ResourceAiAgentDescriptors";
41
+ import PageMap from "../../../../App/FeatureSet/Dashboard/src/Utils/PageMap";
42
+ import {
43
+ RESOURCE_AI_ACCESS_INSIGHTS_PATH,
44
+ RESOURCE_AI_ACCESS_RESET_AGENT_PATH,
45
+ RESOURCE_AI_ACCESS_STATUS_PATH,
46
+ RESOURCE_AI_ACCESS_TEST_PATH,
47
+ } from "../../../Types/AI/ResourceAiAccessApi";
48
+ import {
49
+ RESOURCE_AI_ALLOWLIST_EXAMPLES,
50
+ getResourceSentenceName,
51
+ } from "../../../Types/AI/ResourceAiAccessPermissions";
52
+ import CephCluster from "../../../Models/DatabaseModels/CephCluster";
53
+ import DatabaseServer from "../../../Models/DatabaseModels/DatabaseServer";
54
+ import DockerHost from "../../../Models/DatabaseModels/DockerHost";
55
+ import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
56
+ import Host from "../../../Models/DatabaseModels/Host";
57
+ import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
58
+ import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
59
+ import VMwareVCenter from "../../../Models/DatabaseModels/VMwareVCenter";
60
+ import AiResourceType, {
61
+ AI_RESOURCE_TYPE_INFO,
62
+ ALL_AI_RESOURCE_TYPES,
63
+ } from "../../../Types/ResourceAiAgent/AiResourceType";
64
+ import {
65
+ RESOURCE_AI_AGENT_ALIVE_WINDOW_IN_MINUTES,
66
+ ResourceAiAccessGap,
67
+ ResourceAiAccessGapCode,
68
+ ResourceAiAccessStatus,
69
+ ResourceAiAgentPosture,
70
+ ResourceAiAgentSummary,
71
+ ResourceAiRemediationMode,
72
+ } from "../../../Types/ResourceAiAgent/ResourceAiAccess";
73
+ import ResourceCommandPolicy from "../../../Utils/AiRemediation/Resource/ResourceCommandPolicy";
74
+
75
+ /*
76
+ * The pure half of the resource AI agent page: the descriptors that tell
77
+ * the generic page which resource it is on, and every decision the page
78
+ * makes from the server's ResourceAiAccessStatus — which of its three
79
+ * states the agent card shows, the words for each, the meta line, the
80
+ * gaps it lists and the one action each gap offers.
81
+ */
82
+
83
+ const RESOURCE_ID: string = "44444444-0000-4000-8000-000000000004";
84
+ const AGENT_ID: string = "99999999-0000-4000-8000-000000000009";
85
+ const NOW: Date = new Date("2026-09-29T12:00:00.000Z");
86
+
87
+ const DOCKER: ResourceAiAgentDescriptor = getResourceAiAgentDescriptor(
88
+ AiResourceType.DockerHost,
89
+ );
90
+
91
+ function minutesBefore(minutes: number): string {
92
+ return new Date(NOW.getTime() - minutes * 60 * 1000).toISOString();
93
+ }
94
+
95
+ function makePosture(
96
+ overrides: Partial<ResourceAiAgentPosture> = {},
97
+ ): ResourceAiAgentPosture {
98
+ return {
99
+ resourceType: AiResourceType.DockerHost,
100
+ resourceIdentifier: "prod-docker-01",
101
+ agentVersion: "14.1.0",
102
+ allowWrites: false,
103
+ writeTargets: [],
104
+ protectedTargets: ["oneuptime-docker-ai-agent"],
105
+ toolVersion: "27.3.1",
106
+ reachable: true,
107
+ details: {},
108
+ ...overrides,
109
+ };
110
+ }
111
+
112
+ function makeAgent(
113
+ overrides: Partial<ResourceAiAgentSummary> = {},
114
+ ): ResourceAiAgentSummary {
115
+ return {
116
+ agentId: AGENT_ID,
117
+ connectionStatus: "connected",
118
+ isOnline: true,
119
+ agentVersion: "14.1.0",
120
+ lastAliveAt: minutesBefore(0.5),
121
+ lastRegisteredAt: minutesBefore(60),
122
+ posture: makePosture(),
123
+ ...overrides,
124
+ };
125
+ }
126
+
127
+ function gap(code: ResourceAiAccessGapCode): ResourceAiAccessGap {
128
+ return {
129
+ code,
130
+ title: `Title of ${code}`,
131
+ nextStep: `Next step for ${code}`,
132
+ blocksInvestigation: true,
133
+ blocksRemediation: true,
134
+ };
135
+ }
136
+
137
+ function makeStatus(
138
+ overrides: Partial<ResourceAiAccessStatus> = {},
139
+ ): ResourceAiAccessStatus {
140
+ return {
141
+ resourceType: AiResourceType.DockerHost,
142
+ resourceId: RESOURCE_ID,
143
+ resourceName: "prod-docker-01",
144
+ isAiInvestigationEnabled: true,
145
+ aiRemediationMode: ResourceAiRemediationMode.Disabled,
146
+ aiCommandAllowlist: [],
147
+ agent: makeAgent(),
148
+ gaps: [gap("remediation_disabled")],
149
+ isInvestigationReady: true,
150
+ isRemediationReady: false,
151
+ ...overrides,
152
+ };
153
+ }
154
+
155
+ describe("the descriptors", () => {
156
+ const MODELS: Record<AiResourceType, unknown> = {
157
+ [AiResourceType.DockerHost]: DockerHost,
158
+ [AiResourceType.PodmanHost]: PodmanHost,
159
+ [AiResourceType.DockerSwarmCluster]: DockerSwarmCluster,
160
+ [AiResourceType.ProxmoxCluster]: ProxmoxCluster,
161
+ [AiResourceType.VMwareVCenter]: VMwareVCenter,
162
+ [AiResourceType.CephCluster]: CephCluster,
163
+ [AiResourceType.DatabaseServer]: DatabaseServer,
164
+ [AiResourceType.Host]: Host,
165
+ };
166
+
167
+ const PAGES: Record<AiResourceType, [PageMap, PageMap]> = {
168
+ [AiResourceType.DockerHost]: [
169
+ PageMap.DOCKER_HOST_VIEW_AI_AGENT,
170
+ PageMap.DOCKER_HOST_VIEW_AI_INSIGHTS,
171
+ ],
172
+ [AiResourceType.PodmanHost]: [
173
+ PageMap.PODMAN_HOST_VIEW_AI_AGENT,
174
+ PageMap.PODMAN_HOST_VIEW_AI_INSIGHTS,
175
+ ],
176
+ [AiResourceType.DockerSwarmCluster]: [
177
+ PageMap.DOCKER_SWARM_CLUSTER_VIEW_AI_AGENT,
178
+ PageMap.DOCKER_SWARM_CLUSTER_VIEW_AI_INSIGHTS,
179
+ ],
180
+ [AiResourceType.ProxmoxCluster]: [
181
+ PageMap.PROXMOX_CLUSTER_VIEW_AI_AGENT,
182
+ PageMap.PROXMOX_CLUSTER_VIEW_AI_INSIGHTS,
183
+ ],
184
+ [AiResourceType.VMwareVCenter]: [
185
+ PageMap.VMWARE_VCENTER_VIEW_AI_AGENT,
186
+ PageMap.VMWARE_VCENTER_VIEW_AI_INSIGHTS,
187
+ ],
188
+ [AiResourceType.CephCluster]: [
189
+ PageMap.CEPH_CLUSTER_VIEW_AI_AGENT,
190
+ PageMap.CEPH_CLUSTER_VIEW_AI_INSIGHTS,
191
+ ],
192
+ [AiResourceType.DatabaseServer]: [
193
+ PageMap.DATABASE_SERVER_VIEW_AI_AGENT,
194
+ PageMap.DATABASE_SERVER_VIEW_AI_INSIGHTS,
195
+ ],
196
+ [AiResourceType.Host]: [
197
+ PageMap.HOST_VIEW_AI_AGENT,
198
+ PageMap.HOST_VIEW_AI_INSIGHTS,
199
+ ],
200
+ };
201
+
202
+ test("there is exactly one per resource type, and nothing else", () => {
203
+ expect(Object.keys(RESOURCE_AI_AGENT_DESCRIPTORS).sort()).toEqual(
204
+ [...ALL_AI_RESOURCE_TYPES].sort(),
205
+ );
206
+ });
207
+
208
+ test.each(ALL_AI_RESOURCE_TYPES)(
209
+ "%s: its own type, model and AI pages",
210
+ (type: AiResourceType) => {
211
+ const descriptor: ResourceAiAgentDescriptor =
212
+ getResourceAiAgentDescriptor(type);
213
+
214
+ expect(descriptor.resourceType).toBe(type);
215
+ expect(descriptor.modelType).toBe(MODELS[type]);
216
+ expect(descriptor.agentPage).toBe(PAGES[type][0]);
217
+ expect(descriptor.insightsPage).toBe(PAGES[type][1]);
218
+ },
219
+ );
220
+
221
+ test.each(ALL_AI_RESOURCE_TYPES)(
222
+ "%s: the names come from the shared contract",
223
+ (type: AiResourceType) => {
224
+ const descriptor: ResourceAiAgentDescriptor =
225
+ getResourceAiAgentDescriptor(type);
226
+
227
+ expect(descriptor.agentName).toBe(
228
+ AI_RESOURCE_TYPE_INFO[type].agentDisplayName,
229
+ );
230
+ // The server's sentence names, so the page and its refusals agree.
231
+ expect(descriptor.noun).toBe(getResourceSentenceName(type));
232
+ expect(descriptor.allowlistPlaceholder).toBe(
233
+ RESOURCE_AI_ALLOWLIST_EXAMPLES[type],
234
+ );
235
+ },
236
+ );
237
+
238
+ test.each(ALL_AI_RESOURCE_TYPES)(
239
+ "%s: the allowlist example is an entry the type's policy accepts",
240
+ (type: AiResourceType) => {
241
+ expect(
242
+ ResourceCommandPolicy.describeAllowlistPatternProblem({
243
+ resourceType: type,
244
+ pattern: getResourceAiAgentDescriptor(type).allowlistPlaceholder,
245
+ }),
246
+ ).toBeNull();
247
+ },
248
+ );
249
+
250
+ test("the commands tables keep apart: one preferences key per type", () => {
251
+ const keys: Array<string> = ALL_AI_RESOURCE_TYPES.map(
252
+ (type: AiResourceType): string => {
253
+ return getResourceAiAgentDescriptor(type).commandsTableId;
254
+ },
255
+ );
256
+ expect(new Set(keys).size).toBe(keys.length);
257
+ expect(keys).not.toContain("kubernetes-cluster-ai-kubectl-jobs");
258
+ });
259
+
260
+ test("the identity column is the one the collector's identity lands in", () => {
261
+ expect(
262
+ ALL_AI_RESOURCE_TYPES.map((type: AiResourceType): unknown => {
263
+ return [type, getResourceAiAgentDescriptor(type).identityColumn];
264
+ }),
265
+ ).toEqual([
266
+ [AiResourceType.DockerHost, "hostIdentifier"],
267
+ [AiResourceType.PodmanHost, "hostIdentifier"],
268
+ [AiResourceType.DockerSwarmCluster, "name"],
269
+ [AiResourceType.ProxmoxCluster, "name"],
270
+ [AiResourceType.VMwareVCenter, "name"],
271
+ [AiResourceType.CephCluster, "name"],
272
+ // Pinned by id: database server names are not unique.
273
+ [AiResourceType.DatabaseServer, null],
274
+ [AiResourceType.Host, "hostIdentifier"],
275
+ ]);
276
+ });
277
+
278
+ test.each(ALL_AI_RESOURCE_TYPES)(
279
+ "%s: every sentence of copy is filled in",
280
+ (type: AiResourceType) => {
281
+ const descriptor: ResourceAiAgentDescriptor =
282
+ getResourceAiAgentDescriptor(type);
283
+
284
+ for (const value of [
285
+ descriptor.agentCardDescription,
286
+ descriptor.investigateTitle,
287
+ descriptor.readExamples,
288
+ descriptor.readOnlyCommandsPhrase,
289
+ descriptor.commandsCardTitle,
290
+ descriptor.writeExamples,
291
+ descriptor.riskierExamples,
292
+ ]) {
293
+ expect(value.trim().length).toBeGreaterThan(0);
294
+ }
295
+ expect(descriptor.riskierExamples).toMatch(/^riskier changes such as /);
296
+ },
297
+ );
298
+ });
299
+
300
+ describe("the routes and request body", () => {
301
+ test("are the server's own paths", () => {
302
+ expect(RESOURCE_AI_ACCESS_STATUS_ROUTE).toBe(
303
+ RESOURCE_AI_ACCESS_STATUS_PATH,
304
+ );
305
+ expect(RESOURCE_AI_ACCESS_TEST_ROUTE).toBe(RESOURCE_AI_ACCESS_TEST_PATH);
306
+ expect(RESOURCE_AI_ACCESS_RESET_AGENT_ROUTE).toBe(
307
+ RESOURCE_AI_ACCESS_RESET_AGENT_PATH,
308
+ );
309
+ expect(RESOURCE_AI_ACCESS_INSIGHTS_ROUTE).toBe(
310
+ RESOURCE_AI_ACCESS_INSIGHTS_PATH,
311
+ );
312
+ expect([
313
+ RESOURCE_AI_ACCESS_STATUS_ROUTE,
314
+ RESOURCE_AI_ACCESS_TEST_ROUTE,
315
+ RESOURCE_AI_ACCESS_RESET_AGENT_ROUTE,
316
+ RESOURCE_AI_ACCESS_INSIGHTS_ROUTE,
317
+ ]).toEqual([
318
+ "/resource-ai-access/status",
319
+ "/resource-ai-access/test",
320
+ "/resource-ai-access/reset-agent",
321
+ "/resource-ai-access/insights",
322
+ ]);
323
+ });
324
+
325
+ test.each(ALL_AI_RESOURCE_TYPES)(
326
+ "%s: the body names the type and the resource, nothing else",
327
+ (type: AiResourceType) => {
328
+ expect(
329
+ getResourceAiAccessRequestBody(
330
+ getResourceAiAgentDescriptor(type),
331
+ RESOURCE_ID,
332
+ ),
333
+ ).toEqual({ resourceType: type, resourceId: RESOURCE_ID });
334
+ },
335
+ );
336
+
337
+ test("the page polls as often as the agent heartbeats", () => {
338
+ expect(RESOURCE_AI_AGENT_STATUS_POLL_INTERVAL_MS).toBe(30_000);
339
+ expect(RESOURCE_AI_REFUSED_REGISTRATION_WARNING_WINDOW_MS).toBe(
340
+ 24 * 60 * 60 * 1000,
341
+ );
342
+ expect(RESOURCE_AI_AGENT_PAGE_TITLE).toBe("AI agent");
343
+ });
344
+ });
345
+
346
+ describe("parseResourceAiAccessStatus", () => {
347
+ test("reads the status the route returns", () => {
348
+ const status: ResourceAiAccessStatus = makeStatus();
349
+ expect(
350
+ parseResourceAiAccessStatus(JSON.parse(JSON.stringify(status))),
351
+ ).toEqual(status);
352
+ });
353
+
354
+ test.each([
355
+ ["null", null],
356
+ ["a string", "status"],
357
+ ["an array", [makeStatus()]],
358
+ ["a status without a resource id", { ...makeStatus(), resourceId: 7 }],
359
+ ["a status without gaps", { ...makeStatus(), gaps: "none" }],
360
+ ])("refuses %s", (_label: string, value: unknown) => {
361
+ expect(parseResourceAiAccessStatus(value)).toBeNull();
362
+ });
363
+
364
+ test("reads what it cannot trust the safe way", () => {
365
+ const parsed: ResourceAiAccessStatus | null = parseResourceAiAccessStatus({
366
+ resourceId: RESOURCE_ID,
367
+ gaps: [gap("ai_agent_not_connected"), "not a gap", { title: "no code" }],
368
+ aiRemediationMode: "SomethingNewer",
369
+ aiCommandAllowlist: ["docker stop web", 3, null],
370
+ isAiInvestigationEnabled: "yes",
371
+ isInvestigationReady: 1,
372
+ agent: "an agent",
373
+ });
374
+
375
+ expect(parsed).not.toBeNull();
376
+ // An unknown mode never widens what AI may do.
377
+ expect(parsed!.aiRemediationMode).toBe(ResourceAiRemediationMode.Disabled);
378
+ expect(parsed!.aiCommandAllowlist).toEqual(["docker stop web"]);
379
+ expect(parsed!.isAiInvestigationEnabled).toBe(false);
380
+ expect(parsed!.isInvestigationReady).toBe(false);
381
+ expect(parsed!.isRemediationReady).toBe(false);
382
+ expect(parsed!.agent).toBeNull();
383
+ expect(
384
+ parsed!.gaps.map((item: ResourceAiAccessGap): string => {
385
+ return item.code;
386
+ }),
387
+ ).toEqual(["ai_agent_not_connected"]);
388
+ });
389
+ });
390
+
391
+ describe("the agent card's state", () => {
392
+ test("connected, offline or not installed — nothing else", () => {
393
+ expect(getResourceAiAgentCardState(makeStatus())).toBe("connected");
394
+ expect(
395
+ getResourceAiAgentCardState(
396
+ makeStatus({ agent: makeAgent({ isOnline: false }) }),
397
+ ),
398
+ ).toBe("offline");
399
+ expect(getResourceAiAgentCardState(makeStatus({ agent: null }))).toBe(
400
+ "not_installed",
401
+ );
402
+ });
403
+
404
+ test("the pill says it in one word, coloured by what it means", () => {
405
+ expect(getResourceAiAgentStatusPill(makeStatus())).toEqual({
406
+ text: "Connected",
407
+ tone: "success",
408
+ });
409
+ expect(
410
+ getResourceAiAgentStatusPill(
411
+ makeStatus({ agent: makeAgent({ isOnline: false }) }),
412
+ ),
413
+ ).toEqual({ text: "Offline", tone: "danger" });
414
+ expect(getResourceAiAgentStatusPill(makeStatus({ agent: null }))).toEqual({
415
+ text: "Not installed",
416
+ tone: "neutral",
417
+ });
418
+ });
419
+
420
+ test("an online agent that cannot reach the resource is still connected, but unreachable", () => {
421
+ const status: ResourceAiAccessStatus = makeStatus({
422
+ agent: makeAgent({
423
+ posture: makePosture({
424
+ reachable: false,
425
+ reachError: "Cannot connect to the Docker daemon",
426
+ }),
427
+ }),
428
+ });
429
+
430
+ expect(getResourceAiAgentCardState(status)).toBe("connected");
431
+ expect(isResourceUnreachable(status)).toBe(true);
432
+ expect(isResourceUnreachable(makeStatus())).toBe(false);
433
+ // An offline agent's last probe says nothing about now.
434
+ expect(
435
+ isResourceUnreachable(
436
+ makeStatus({
437
+ agent: makeAgent({
438
+ isOnline: false,
439
+ posture: makePosture({ reachable: false }),
440
+ }),
441
+ }),
442
+ ),
443
+ ).toBe(false);
444
+ });
445
+ });
446
+
447
+ describe("why an offline agent is offline", () => {
448
+ test("never signed off: it went silent", () => {
449
+ expect(
450
+ getResourceAiAgentOfflineReason(
451
+ makeStatus({
452
+ agent: makeAgent({ isOnline: false, lastAliveAt: minutesBefore(12) }),
453
+ }),
454
+ NOW,
455
+ ),
456
+ ).toBe("silent");
457
+ });
458
+
459
+ test("signed off inside the alive window: it is coming back", () => {
460
+ expect(
461
+ getResourceAiAgentOfflineReason(
462
+ makeStatus({
463
+ agent: makeAgent({
464
+ isOnline: false,
465
+ connectionStatus: "disconnected",
466
+ lastAliveAt: minutesBefore(1),
467
+ }),
468
+ }),
469
+ NOW,
470
+ ),
471
+ ).toBe("signed_off");
472
+ });
473
+
474
+ test("signed off exactly at the window's edge still reads as coming back", () => {
475
+ expect(
476
+ getResourceAiAgentOfflineReason(
477
+ makeStatus({
478
+ agent: makeAgent({
479
+ isOnline: false,
480
+ connectionStatus: "disconnected",
481
+ lastAliveAt: minutesBefore(
482
+ RESOURCE_AI_AGENT_ALIVE_WINDOW_IN_MINUTES,
483
+ ),
484
+ }),
485
+ }),
486
+ NOW,
487
+ ),
488
+ ).toBe("signed_off");
489
+ });
490
+
491
+ test("signed off long ago, or never heard from: gone", () => {
492
+ for (const lastAliveAt of [minutesBefore(45), null, "not a date"]) {
493
+ expect(
494
+ getResourceAiAgentOfflineReason(
495
+ makeStatus({
496
+ agent: makeAgent({
497
+ isOnline: false,
498
+ connectionStatus: "disconnected",
499
+ lastAliveAt,
500
+ }),
501
+ }),
502
+ NOW,
503
+ ),
504
+ ).toBe("gone");
505
+ }
506
+ });
507
+ });
508
+
509
+ describe("the card's sentence", () => {
510
+ test.each(ALL_AI_RESOURCE_TYPES)(
511
+ "%s: names the agent and the resource in every state",
512
+ (type: AiResourceType) => {
513
+ const descriptor: ResourceAiAgentDescriptor =
514
+ getResourceAiAgentDescriptor(type);
515
+
516
+ expect(
517
+ getResourceAiAgentStateSentence(makeStatus(), descriptor, NOW),
518
+ ).toBe(
519
+ `The ${descriptor.agentName} is running next to this ${descriptor.noun}.`,
520
+ );
521
+ expect(
522
+ getResourceAiAgentStateSentence(
523
+ makeStatus({ agent: null }),
524
+ descriptor,
525
+ NOW,
526
+ ),
527
+ ).toBe(getResourceAiAgentNotInstalledText(descriptor));
528
+ expect(getResourceAiAgentNotInstalledText(descriptor)).toContain(
529
+ descriptor.agentName,
530
+ );
531
+ expect(getResourceAiAgentNotInstalledText(descriptor)).toContain(
532
+ "read-only by default",
533
+ );
534
+ },
535
+ );
536
+
537
+ test("offline, each way, ending where the logs command takes over", () => {
538
+ expect(
539
+ getResourceAiAgentStateSentence(
540
+ makeStatus({
541
+ agent: makeAgent({ isOnline: false, lastAliveAt: minutesBefore(12) }),
542
+ }),
543
+ DOCKER,
544
+ NOW,
545
+ ),
546
+ ).toBe(getResourceAiAgentSilentText(DOCKER));
547
+ expect(getResourceAiAgentSilentText(DOCKER)).toBe(
548
+ "The Docker AI agent has not checked in for over 5 minutes. Check its logs:",
549
+ );
550
+
551
+ expect(
552
+ getResourceAiAgentStateSentence(
553
+ makeStatus({
554
+ agent: makeAgent({
555
+ isOnline: false,
556
+ connectionStatus: "disconnected",
557
+ lastAliveAt: minutesBefore(1),
558
+ }),
559
+ }),
560
+ DOCKER,
561
+ NOW,
562
+ ),
563
+ ).toBe(getResourceAiAgentSignedOffText(DOCKER));
564
+ expect(getResourceAiAgentSignedOffText(DOCKER)).toBe(
565
+ "The Docker AI agent signed off or was reset. It reconnects on its own within a few minutes. If it does not, check its logs:",
566
+ );
567
+
568
+ expect(
569
+ getResourceAiAgentStateSentence(
570
+ makeStatus({
571
+ agent: makeAgent({
572
+ isOnline: false,
573
+ connectionStatus: "disconnected",
574
+ lastAliveAt: minutesBefore(45),
575
+ }),
576
+ }),
577
+ DOCKER,
578
+ NOW,
579
+ ),
580
+ ).toBe(getResourceAiAgentGoneText(DOCKER));
581
+ expect(getResourceAiAgentGoneText(DOCKER)).toBe(
582
+ "The Docker AI agent disconnected and has not come back. Check its logs:",
583
+ );
584
+ });
585
+
586
+ test("online but unable to reach the resource: says so, with the agent's reason", () => {
587
+ expect(
588
+ getResourceAiAgentStateSentence(
589
+ makeStatus({
590
+ agent: makeAgent({
591
+ posture: makePosture({
592
+ reachable: false,
593
+ reachError: "Cannot connect to the Docker daemon",
594
+ }),
595
+ }),
596
+ }),
597
+ DOCKER,
598
+ NOW,
599
+ ),
600
+ ).toBe(
601
+ "The Docker AI agent is running, but it could not reach this Docker host at its last check: Cannot connect to the Docker daemon Check its logs:",
602
+ );
603
+ expect(
604
+ getResourceAiAgentStateSentence(
605
+ makeStatus({
606
+ agent: makeAgent({ posture: makePosture({ reachable: false }) }),
607
+ }),
608
+ DOCKER,
609
+ NOW,
610
+ ),
611
+ ).toBe(
612
+ "The Docker AI agent is running, but it could not reach this Docker host at its last check. Check its logs:",
613
+ );
614
+ });
615
+
616
+ test("the heading, ready line and hint say which resource", () => {
617
+ const host: ResourceAiAgentDescriptor = getResourceAiAgentDescriptor(
618
+ AiResourceType.Host,
619
+ );
620
+ expect(getResourceAiAgentPageSubtitle(host)).toBe(
621
+ "Whether OneUptime AI can reach this host, and what it may do there.",
622
+ );
623
+ expect(getResourceAiAgentReadyText(DOCKER)).toBe(
624
+ "Ready — AI will inspect this Docker host with read-only docker commands when it investigates an incident or alert here.",
625
+ );
626
+ });
627
+ });
628
+
629
+ describe("the command under the sentence", () => {
630
+ test("install when nothing registered, logs when offline or unreachable, none otherwise", () => {
631
+ expect(getResourceAiAgentCardCommand(makeStatus({ agent: null }))).toBe(
632
+ "install",
633
+ );
634
+ expect(
635
+ getResourceAiAgentCardCommand(
636
+ makeStatus({ agent: makeAgent({ isOnline: false }) }),
637
+ ),
638
+ ).toBe("logs");
639
+ expect(
640
+ getResourceAiAgentCardCommand(
641
+ makeStatus({
642
+ agent: makeAgent({ posture: makePosture({ reachable: false }) }),
643
+ }),
644
+ ),
645
+ ).toBe("logs");
646
+ expect(getResourceAiAgentCardCommand(makeStatus())).toBeNull();
647
+ });
648
+ });
649
+
650
+ describe("the meta line", () => {
651
+ test("last seen, the agent's version, the resource's version and what it may change", () => {
652
+ const parts: Array<string> = getResourceAiAgentMetaParts(
653
+ makeStatus(),
654
+ DOCKER,
655
+ );
656
+
657
+ expect(parts[0]).toMatch(/^last seen /);
658
+ expect(parts.slice(1)).toEqual([
659
+ "agent v14.1.0",
660
+ "Docker 27.3.1",
661
+ "Read-only",
662
+ ]);
663
+ });
664
+
665
+ test("is empty before an agent registered", () => {
666
+ expect(
667
+ getResourceAiAgentMetaParts(makeStatus({ agent: null }), DOCKER),
668
+ ).toEqual([]);
669
+ });
670
+
671
+ test("leaves out what the agent did not report", () => {
672
+ expect(
673
+ getResourceAiAgentMetaParts(
674
+ makeStatus({
675
+ agent: makeAgent({
676
+ lastAliveAt: null,
677
+ agentVersion: null,
678
+ posture: null,
679
+ }),
680
+ }),
681
+ DOCKER,
682
+ ),
683
+ ).toEqual([]);
684
+ });
685
+
686
+ test("falls back to the posture's agent version", () => {
687
+ expect(
688
+ getResourceAiAgentMetaParts(
689
+ makeStatus({
690
+ agent: makeAgent({
691
+ lastAliveAt: null,
692
+ agentVersion: null,
693
+ posture: makePosture({
694
+ agentVersion: "v14.2.0",
695
+ toolVersion: null,
696
+ }),
697
+ }),
698
+ }),
699
+ DOCKER,
700
+ ),
701
+ ).toEqual(["agent v14.2.0", "Read-only"]);
702
+ });
703
+
704
+ test("what the agent may change, from its posture", () => {
705
+ expect(describeResourceAiAgentWriteAccess(null)).toBeNull();
706
+ expect(describeResourceAiAgentWriteAccess(makePosture())).toBe("Read-only");
707
+ expect(
708
+ describeResourceAiAgentWriteAccess(makePosture({ allowWrites: true })),
709
+ ).toBe("Can change any target");
710
+ expect(
711
+ describeResourceAiAgentWriteAccess(
712
+ makePosture({ allowWrites: true, writeTargets: ["web-*", "api", " "] }),
713
+ ),
714
+ ).toBe("Can change: web-*, api");
715
+ });
716
+
717
+ test.each([
718
+ [AiResourceType.DockerHost, "27.3.1", {}, "Docker 27.3.1"],
719
+ [AiResourceType.PodmanHost, "5.2.0", {}, "Podman 5.2.0"],
720
+ [AiResourceType.ProxmoxCluster, "8.2.4", {}, "Proxmox VE 8.2.4"],
721
+ [AiResourceType.VMwareVCenter, "8.0.3", {}, "vCenter 8.0.3"],
722
+ [AiResourceType.CephCluster, "19.2.0", {}, "Ceph 19.2.0"],
723
+ // Already named: not "Ceph ceph version 19.2.0".
724
+ [
725
+ AiResourceType.CephCluster,
726
+ "ceph version 19.2.0",
727
+ {},
728
+ "ceph version 19.2.0",
729
+ ],
730
+ [
731
+ AiResourceType.DatabaseServer,
732
+ "16.4",
733
+ { databaseSystem: "postgresql" },
734
+ "postgresql 16.4",
735
+ ],
736
+ [AiResourceType.DatabaseServer, "16.4", {}, "16.4"],
737
+ [AiResourceType.Host, "6.8.0-45-generic", {}, "6.8.0-45-generic"],
738
+ ])(
739
+ "%s reports %s as it should read",
740
+ (
741
+ type: AiResourceType,
742
+ version: string,
743
+ details: Record<string, string>,
744
+ expected: string,
745
+ ) => {
746
+ expect(
747
+ formatResourceToolVersion(
748
+ getResourceAiAgentDescriptor(type),
749
+ makePosture({ resourceType: type, toolVersion: version, details }),
750
+ ),
751
+ ).toBe(expected);
752
+ },
753
+ );
754
+
755
+ test("no version, no part", () => {
756
+ expect(formatResourceToolVersion(DOCKER, null)).toBeNull();
757
+ expect(
758
+ formatResourceToolVersion(DOCKER, makePosture({ toolVersion: " " })),
759
+ ).toBeNull();
760
+ });
761
+ });
762
+
763
+ describe("the refused-registration warning", () => {
764
+ test("names the identity variables of the resource type", () => {
765
+ const warning: string | null = getResourceAiRefusedRegistrationWarning(
766
+ makeAgent({
767
+ lastRefusedRegistrationAt: minutesBefore(10),
768
+ lastRefusedRegistrationReason: "previous_instance_online",
769
+ }),
770
+ DOCKER,
771
+ NOW,
772
+ );
773
+
774
+ expect(warning).toContain(
775
+ "Another agent tried to register for this Docker host at",
776
+ );
777
+ expect(warning).toContain("the same DOCKER_HOST_NAME");
778
+
779
+ expect(
780
+ getResourceAiRefusedRegistrationWarning(
781
+ makeAgent({ lastRefusedRegistrationAt: minutesBefore(10) }),
782
+ getResourceAiAgentDescriptor(AiResourceType.DatabaseServer),
783
+ NOW,
784
+ ),
785
+ ).toContain(
786
+ "DATABASE_SERVER_ID / DATABASE_SYSTEM / DATABASE_SERVER_ADDRESS / DATABASE_SERVER_PORT",
787
+ );
788
+ });
789
+
790
+ test("says nothing without a refusal, for another reason, a bad date or an old one", () => {
791
+ expect(
792
+ getResourceAiRefusedRegistrationWarning(null, DOCKER, NOW),
793
+ ).toBeNull();
794
+ expect(
795
+ getResourceAiRefusedRegistrationWarning(makeAgent(), DOCKER, NOW),
796
+ ).toBeNull();
797
+ expect(
798
+ getResourceAiRefusedRegistrationWarning(
799
+ makeAgent({
800
+ lastRefusedRegistrationAt: minutesBefore(10),
801
+ lastRefusedRegistrationReason: "agent_cap_reached",
802
+ }),
803
+ DOCKER,
804
+ NOW,
805
+ ),
806
+ ).toBeNull();
807
+ expect(
808
+ getResourceAiRefusedRegistrationWarning(
809
+ makeAgent({ lastRefusedRegistrationAt: "yesterday-ish" }),
810
+ DOCKER,
811
+ NOW,
812
+ ),
813
+ ).toBeNull();
814
+ expect(
815
+ getResourceAiRefusedRegistrationWarning(
816
+ makeAgent({ lastRefusedRegistrationAt: minutesBefore(25 * 60) }),
817
+ DOCKER,
818
+ NOW,
819
+ ),
820
+ ).toBeNull();
821
+ });
822
+ });
823
+
824
+ describe("Needs attention", () => {
825
+ test("leaves out the fixes-off choice, keeps every problem", () => {
826
+ expect(RESOURCE_AI_CHOICE_GAP_CODES).toEqual(["remediation_disabled"]);
827
+ expect(
828
+ getResourceAiAttentionGaps(
829
+ makeStatus({
830
+ gaps: [
831
+ gap("remediation_disabled"),
832
+ gap("llm_provider_missing"),
833
+ gap("ai_agent_offline"),
834
+ ],
835
+ }),
836
+ ).map((item: ResourceAiAccessGap): string => {
837
+ return item.code;
838
+ }),
839
+ ).toEqual(["llm_provider_missing", "ai_agent_offline"]);
840
+ });
841
+
842
+ test.each([
843
+ ["investigation_disabled", "turn_on_investigation"],
844
+ ["ai_disabled_for_project", "open_ai_features"],
845
+ ["auto_remediation_disabled_for_project", "open_ai_features"],
846
+ ["llm_provider_missing", "open_llm_providers"],
847
+ ["ai_balance_insufficient", "open_ai_credits"],
848
+ ["ai_agent_unreachable_resource", "test_connection"],
849
+ // Their next step is a command already on the page.
850
+ ["ai_agent_not_connected", null],
851
+ ["ai_agent_offline", null],
852
+ ["remediation_write_access_missing", null],
853
+ ["remediation_disabled", null],
854
+ ])("%s offers %s", (code: string, action: string | null) => {
855
+ expect(
856
+ getResourceAiAgentGapAction(
857
+ gap(code as ResourceAiAccessGapCode),
858
+ makeStatus(),
859
+ ),
860
+ ).toBe(action);
861
+ });
862
+
863
+ test("an unreachable resource offers no test without an agent to run it", () => {
864
+ expect(
865
+ getResourceAiAgentGapAction(
866
+ gap("ai_agent_unreachable_resource"),
867
+ makeStatus({ agent: null }),
868
+ ),
869
+ ).toBeNull();
870
+ });
871
+ });
872
+
873
+ describe("the write-access instructions", () => {
874
+ test("only once fixes are on and the agent reports it is read-only", () => {
875
+ expect(shouldShowResourceWriteAccessCommands(makeStatus())).toBe(false);
876
+ expect(
877
+ shouldShowResourceWriteAccessCommands(
878
+ makeStatus({
879
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
880
+ }),
881
+ ),
882
+ ).toBe(true);
883
+ expect(
884
+ shouldShowResourceWriteAccessCommands(
885
+ makeStatus({
886
+ aiRemediationMode: ResourceAiRemediationMode.Automatic,
887
+ agent: makeAgent({ posture: makePosture({ allowWrites: true }) }),
888
+ }),
889
+ ),
890
+ ).toBe(false);
891
+ // The install instructions come first.
892
+ expect(
893
+ shouldShowResourceWriteAccessCommands(
894
+ makeStatus({
895
+ aiRemediationMode: ResourceAiRemediationMode.BypassApproval,
896
+ agent: null,
897
+ }),
898
+ ),
899
+ ).toBe(false);
900
+ // An agent that never reported a posture is not known to write.
901
+ expect(
902
+ shouldShowResourceWriteAccessCommands(
903
+ makeStatus({
904
+ aiRemediationMode: ResourceAiRemediationMode.RequireApproval,
905
+ agent: makeAgent({ posture: null }),
906
+ }),
907
+ ),
908
+ ).toBe(true);
909
+ });
910
+ });
911
+
912
+ describe("the AI Insights page's pointer to the agent page", () => {
913
+ test("only when AI cannot run commands on the resource", () => {
914
+ expect(getResourceAiAgentPageHint(null, DOCKER)).toBeNull();
915
+ expect(getResourceAiAgentPageHint(makeStatus(), DOCKER)).toBeNull();
916
+ expect(
917
+ getResourceAiAgentPageHint(
918
+ makeStatus({ isInvestigationReady: false }),
919
+ getResourceAiAgentDescriptor(AiResourceType.CephCluster),
920
+ ),
921
+ ).toBe("OneUptime AI can't run commands on this Ceph cluster right now.");
922
+ });
923
+ });
924
+
925
+ describe("parseResourceAccessTestResult", () => {
926
+ test("reads the test route's answer", () => {
927
+ expect(
928
+ parseResourceAccessTestResult({
929
+ ok: true,
930
+ message: "Both commands worked.",
931
+ results: [
932
+ {
933
+ command: "docker version",
934
+ succeeded: true,
935
+ exitCode: 0,
936
+ output: "Server: 27.3.1",
937
+ errorMessage: null,
938
+ },
939
+ {
940
+ command: "docker info",
941
+ succeeded: false,
942
+ exitCode: null,
943
+ output: "",
944
+ errorMessage: "Refused by the Docker AI agent",
945
+ },
946
+ ],
947
+ status: makeStatus(),
948
+ }),
949
+ ).toEqual({
950
+ ok: true,
951
+ message: "Both commands worked.",
952
+ results: [
953
+ {
954
+ command: "docker version",
955
+ succeeded: true,
956
+ exitCode: 0,
957
+ output: "Server: 27.3.1",
958
+ errorMessage: null,
959
+ },
960
+ {
961
+ command: "docker info",
962
+ succeeded: false,
963
+ exitCode: null,
964
+ output: "",
965
+ errorMessage: "Refused by the Docker AI agent",
966
+ },
967
+ ],
968
+ });
969
+ });
970
+
971
+ test("reads anything else as a failed test with nothing to show", () => {
972
+ const empty: ResourceAccessTestResult = {
973
+ ok: false,
974
+ message: "",
975
+ results: [],
976
+ };
977
+ expect(parseResourceAccessTestResult(null)).toEqual(empty);
978
+ expect(parseResourceAccessTestResult({ ok: "yes", results: {} })).toEqual(
979
+ empty,
980
+ );
981
+ expect(
982
+ parseResourceAccessTestResult({
983
+ results: [null, { exitCode: Infinity, succeeded: "true" }],
984
+ }).results,
985
+ ).toEqual([
986
+ {
987
+ command: "",
988
+ succeeded: false,
989
+ exitCode: null,
990
+ output: "",
991
+ errorMessage: null,
992
+ },
993
+ {
994
+ command: "",
995
+ succeeded: false,
996
+ exitCode: null,
997
+ output: "",
998
+ errorMessage: null,
999
+ },
1000
+ ]);
1001
+ });
1002
+ });