@oneuptime/common 11.5.7 → 11.5.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +15 -6
  2. package/Models/DatabaseModels/AIRun.ts +68 -0
  3. package/Models/DatabaseModels/AIRunEvent.ts +34 -1
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
  5. package/Models/DatabaseModels/Project.ts +25 -0
  6. package/Models/DatabaseModels/StatusPage.ts +42 -0
  7. package/Server/API/AIAgentTaskAPI.ts +38 -12
  8. package/Server/API/AIAgentTaskLogAPI.ts +49 -0
  9. package/Server/API/AIChatAPI.ts +16 -0
  10. package/Server/API/AIReadinessAPI.ts +84 -0
  11. package/Server/API/CodeFixRunAPI.ts +190 -57
  12. package/Server/API/TelemetryExceptionAPI.ts +6 -1
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  19. package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
  20. package/Server/Middleware/ProjectAuthorization.ts +1 -1
  21. package/Server/Services/AIRunEventService.ts +15 -1
  22. package/Server/Services/AIRunService.ts +48 -12
  23. package/Server/Services/LlmProviderService.ts +2 -0
  24. package/Server/Services/ProjectService.ts +42 -0
  25. package/Server/Services/StatusPageService.ts +87 -0
  26. package/Server/Services/TelemetryExceptionService.ts +19 -90
  27. package/Server/Types/Markdown.ts +9 -9
  28. package/Server/Types/MarkdownSlugify.ts +47 -0
  29. package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
  30. package/Server/Utils/AI/AIRunTranscript.ts +240 -0
  31. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
  32. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
  33. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
  34. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
  35. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
  36. package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
  37. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
  38. package/Server/Utils/AI/Toolbox/Index.ts +27 -0
  39. package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
  40. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
  41. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
  42. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
  43. package/Server/Utils/LLM/LLMService.ts +397 -18
  44. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
  45. package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
  46. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
  47. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
  48. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
  49. package/Tests/Server/Types/Markdown.test.ts +106 -0
  50. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
  51. package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
  52. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
  53. package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
  54. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
  55. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
  56. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
  57. package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
  58. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
  59. package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
  60. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
  61. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
  62. package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
  63. package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
  64. package/Tests/Types/Log/LogSeverity.test.ts +78 -0
  65. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
  66. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
  67. package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
  68. package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
  69. package/Types/AI/AIAgentTaskStatus.ts +13 -0
  70. package/Types/AI/AIChatPageContext.ts +137 -0
  71. package/Types/AI/AIChatTypes.ts +44 -0
  72. package/Types/AI/AIFixReadiness.ts +46 -0
  73. package/Types/AI/AIRunStatus.ts +11 -0
  74. package/Types/Email/EmailTemplateType.ts +2 -0
  75. package/Types/Log/LogSeverity.ts +61 -0
  76. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
  77. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
  78. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
  79. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
  80. package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
  81. package/UI/Components/Forms/BasicForm.tsx +26 -8
  82. package/UI/Styles/Theme.css +1314 -0
  83. package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
  84. package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
  85. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
  86. package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
  87. package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
  88. package/UI/Utils/DownloadFile.ts +54 -0
  89. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
  90. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  91. package/build/dist/Models/DatabaseModels/AIRun.js +69 -0
  92. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  93. package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
  94. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
  95. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
  96. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/Project.js +27 -0
  98. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
  100. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  101. package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
  102. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  103. package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
  104. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  105. package/build/dist/Server/API/AIChatAPI.js +9 -0
  106. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  107. package/build/dist/Server/API/AIReadinessAPI.js +57 -0
  108. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
  109. package/build/dist/Server/API/CodeFixRunAPI.js +165 -51
  110. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  111. package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
  112. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  125. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
  126. package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
  127. package/build/dist/Server/Services/AIRunEventService.js +8 -0
  128. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  129. package/build/dist/Server/Services/AIRunService.js +39 -3
  130. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  131. package/build/dist/Server/Services/LlmProviderService.js +2 -0
  132. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  133. package/build/dist/Server/Services/ProjectService.js +39 -0
  134. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  135. package/build/dist/Server/Services/StatusPageService.js +81 -0
  136. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  137. package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
  138. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  139. package/build/dist/Server/Types/Markdown.js +8 -8
  140. package/build/dist/Server/Types/Markdown.js.map +1 -1
  141. package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
  142. package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
  143. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
  144. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
  145. package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
  146. package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
  147. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
  148. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  149. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
  150. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  151. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
  152. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  153. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
  154. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
  155. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  156. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
  157. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
  158. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
  159. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
  160. package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
  161. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  162. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
  163. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
  164. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
  165. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
  166. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
  167. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  168. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
  169. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
  170. package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
  171. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
  173. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  174. package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
  175. package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
  176. package/build/dist/Types/AI/AIChatPageContext.js +101 -0
  177. package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
  178. package/build/dist/Types/AI/AIChatTypes.js +2 -0
  179. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  180. package/build/dist/Types/AI/AIFixReadiness.js +9 -0
  181. package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
  182. package/build/dist/Types/AI/AIRunStatus.js +11 -0
  183. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  184. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  185. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  186. package/build/dist/Types/Log/LogSeverity.js +56 -0
  187. package/build/dist/Types/Log/LogSeverity.js.map +1 -1
  188. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
  189. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
  190. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
  191. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
  193. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
  195. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  196. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
  197. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  198. package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
  199. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  200. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
  201. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
  202. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
  203. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
  204. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
  205. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
  206. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
  207. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
  208. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
  209. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
  210. package/build/dist/UI/Utils/DownloadFile.js +41 -0
  211. package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
  212. package/package.json +5 -1
@@ -0,0 +1,596 @@
1
+ import {
2
+ FindCodeForExceptionTool,
3
+ ListCodeRepositoriesTool,
4
+ ReadCodeFileTool,
5
+ SearchCodeTool,
6
+ } from "../../../../Server/Utils/AI/Toolbox/CodeTools";
7
+ import {
8
+ ToolContext,
9
+ ToolExecutionResult,
10
+ } from "../../../../Server/Utils/AI/Toolbox/ToolTypes";
11
+ import CodeRepositoryService from "../../../../Server/Services/CodeRepositoryService";
12
+ import TelemetryExceptionService from "../../../../Server/Services/TelemetryExceptionService";
13
+ import GitHubUtil from "../../../../Server/Utils/CodeRepository/GitHub/GitHub";
14
+ import CodeRepositoryModel from "../../../../Models/DatabaseModels/CodeRepository";
15
+ import TelemetryException from "../../../../Models/DatabaseModels/TelemetryException";
16
+ import CodeRepositoryType from "../../../../Types/CodeRepository/CodeRepositoryType";
17
+ import ObjectID from "../../../../Types/ObjectID";
18
+ import { describe, expect, test, afterEach } from "@jest/globals";
19
+
20
+ /*
21
+ * The code tools are the bridge from a telemetry signal to the source that
22
+ * produced it. These tests mock the repository/exception services and the
23
+ * GitHub Contents API (no network, no clone) to lock in the behaviours that
24
+ * are load-bearing for trust:
25
+ * (a) a repository that is not readable (GitLab, or a GitHub row whose App
26
+ * installation is gone) is invisible to the model — it must never look
27
+ * readable;
28
+ * (b) ambiguity between several repositories is an error the model can fix,
29
+ * never a guess at which codebase the question is about;
30
+ * (c) read_code_file's line window resolves and clamps correctly, and lines
31
+ * carry their real file line numbers so the model can cite them;
32
+ * (d) secrets in source are redacted before they egress to the provider;
33
+ * (e) an exception with no stack trace, or that matches no repository, is
34
+ * reported honestly instead of guessed at.
35
+ */
36
+
37
+ const ctx: ToolContext = {
38
+ projectId: ObjectID.generate(),
39
+ props: { isRoot: true },
40
+ };
41
+
42
+ const REPO_ID: ObjectID = ObjectID.generate();
43
+
44
+ function buildRepository(data?: {
45
+ id?: ObjectID;
46
+ name?: string;
47
+ hostedAt?: CodeRepositoryType;
48
+ installationId?: string | undefined;
49
+ }): CodeRepositoryModel {
50
+ const repository: CodeRepositoryModel = new CodeRepositoryModel();
51
+ repository._id = (data?.id ?? REPO_ID).toString();
52
+ repository.name = data?.name ?? "checkout";
53
+ repository.organizationName = "acme";
54
+ repository.repositoryName = "checkout";
55
+ repository.mainBranchName = "main";
56
+ repository.repositoryHostedAt = data?.hostedAt ?? CodeRepositoryType.GitHub;
57
+
58
+ if (data?.installationId !== undefined) {
59
+ repository.gitHubAppInstallationId = data.installationId;
60
+ } else {
61
+ repository.gitHubAppInstallationId = "12345";
62
+ }
63
+
64
+ return repository;
65
+ }
66
+
67
+ function mockRepositories(repositories: Array<CodeRepositoryModel>): void {
68
+ jest
69
+ .spyOn(CodeRepositoryService, "findBy")
70
+ .mockResolvedValue(repositories as never);
71
+ }
72
+
73
+ function mockFileContent(content: string): void {
74
+ const lines: Array<string> = content === "" ? [] : content.split("\n");
75
+
76
+ if (lines[lines.length - 1] === "") {
77
+ lines.pop();
78
+ }
79
+
80
+ jest.spyOn(GitHubUtil, "getFileContent").mockResolvedValue({
81
+ content: content,
82
+ filePath: "src/billing/charge.ts",
83
+ sizeInBytes: content.length,
84
+ totalLines: lines.length,
85
+ htmlUrl: "https://github.com/acme/checkout/blob/main/src/billing/charge.ts",
86
+ });
87
+ }
88
+
89
+ afterEach(() => {
90
+ jest.restoreAllMocks();
91
+ });
92
+
93
+ describe("list_code_repositories", () => {
94
+ test("hides repositories that cannot actually be read", async () => {
95
+ mockRepositories([
96
+ buildRepository({ name: "readable" }),
97
+ // GitHub row whose App installation was uninstalled.
98
+ buildRepository({
99
+ id: ObjectID.generate(),
100
+ name: "uninstalled",
101
+ installationId: "",
102
+ }),
103
+ // GitLab has no read path yet.
104
+ buildRepository({
105
+ id: ObjectID.generate(),
106
+ name: "gitlab-repo",
107
+ hostedAt: CodeRepositoryType.GitLab,
108
+ }),
109
+ ]);
110
+
111
+ const result: ToolExecutionResult = await ListCodeRepositoriesTool.execute(
112
+ {},
113
+ ctx,
114
+ );
115
+
116
+ expect(result.dataForLlm).toContain("readable");
117
+ expect(result.dataForLlm).not.toContain("uninstalled");
118
+ expect(result.dataForLlm).not.toContain("gitlab-repo");
119
+ expect(result.rowCount).toBe(1);
120
+ });
121
+
122
+ test("says plainly when no repository is connected", async () => {
123
+ mockRepositories([]);
124
+
125
+ const result: ToolExecutionResult = await ListCodeRepositoriesTool.execute(
126
+ {},
127
+ ctx,
128
+ );
129
+
130
+ expect(result.rowCount).toBe(0);
131
+ expect(result.dataForLlm).toContain(
132
+ "no GitHub-connected code repositories",
133
+ );
134
+ });
135
+ });
136
+
137
+ describe("read_code_file — repository targeting", () => {
138
+ test("refuses to guess when several repositories could match", async () => {
139
+ mockRepositories([
140
+ buildRepository({ id: REPO_ID, name: "checkout" }),
141
+ buildRepository({ id: ObjectID.generate(), name: "billing" }),
142
+ ]);
143
+
144
+ await expect(
145
+ ReadCodeFileTool.execute({ filePath: "src/index.ts" }, ctx),
146
+ ).rejects.toThrow(/repositoryId is required/);
147
+ });
148
+
149
+ test("does not require repositoryId when exactly one repository exists", async () => {
150
+ mockRepositories([buildRepository()]);
151
+ mockFileContent("const a = 1;");
152
+
153
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
154
+ { filePath: "src/billing/charge.ts" },
155
+ ctx,
156
+ );
157
+
158
+ expect(result.dataForLlm).toContain("const a = 1;");
159
+ });
160
+
161
+ test("explains when the project has no connected repository at all", async () => {
162
+ mockRepositories([]);
163
+
164
+ await expect(
165
+ ReadCodeFileTool.execute({ filePath: "src/index.ts" }, ctx),
166
+ ).rejects.toThrow(/no GitHub-connected code repositories/);
167
+ });
168
+
169
+ test("reports a missing file as fixable rather than crashing", async () => {
170
+ mockRepositories([buildRepository()]);
171
+ jest.spyOn(GitHubUtil, "getFileContent").mockResolvedValue(null);
172
+
173
+ await expect(
174
+ ReadCodeFileTool.execute({ filePath: "src/nope.ts" }, ctx),
175
+ ).rejects.toThrow(/No readable file at "src\/nope.ts"/);
176
+ });
177
+ });
178
+
179
+ describe("read_code_file — line windows", () => {
180
+ const file: string = Array.from({ length: 500 }, (_v: unknown, i: number) => {
181
+ return `line ${i + 1}`;
182
+ }).join("\n");
183
+
184
+ test("numbers lines with their real file line numbers", async () => {
185
+ mockRepositories([buildRepository()]);
186
+ mockFileContent(file);
187
+
188
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
189
+ { filePath: "src/billing/charge.ts", startLine: 10, endLine: 12 },
190
+ ctx,
191
+ );
192
+
193
+ expect(result.dataForLlm).toContain("10| line 10");
194
+ expect(result.dataForLlm).toContain("12| line 12");
195
+ expect(result.dataForLlm).not.toContain("line 13");
196
+ expect(result.dataForLlm).toContain("showing lines 10-12 of 500");
197
+ expect(result.rowCount).toBe(3);
198
+ });
199
+
200
+ test("aroundLine centres a window on a stack-trace line", async () => {
201
+ mockRepositories([buildRepository()]);
202
+ mockFileContent(file);
203
+
204
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
205
+ { filePath: "src/billing/charge.ts", aroundLine: 200 },
206
+ ctx,
207
+ );
208
+
209
+ // 60 lines either side of 200.
210
+ expect(result.dataForLlm).toContain("showing lines 140-260 of 500");
211
+ expect(result.dataForLlm).toContain("200| line 200");
212
+ });
213
+
214
+ test("aroundLine near the top of a file does not run off the start", async () => {
215
+ mockRepositories([buildRepository()]);
216
+ mockFileContent(file);
217
+
218
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
219
+ { filePath: "src/billing/charge.ts", aroundLine: 3 },
220
+ ctx,
221
+ );
222
+
223
+ expect(result.dataForLlm).toContain("showing lines 1-63 of 500");
224
+ expect(result.dataForLlm).toContain("1| line 1");
225
+ });
226
+
227
+ test("caps an oversized window and tells the model how to read on", async () => {
228
+ mockRepositories([buildRepository()]);
229
+ mockFileContent(file);
230
+
231
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
232
+ { filePath: "src/billing/charge.ts", startLine: 1, endLine: 500 },
233
+ ctx,
234
+ );
235
+
236
+ expect(result.isTruncated).toBe(true);
237
+ expect(result.dataForLlm).toContain("showing lines 1-400 of 500");
238
+ // The hint must resume exactly where the read stopped — no skipped lines.
239
+ expect(result.dataForLlm).toContain("startLine=401");
240
+ });
241
+
242
+ test("redacts secrets found in source before they reach the provider", async () => {
243
+ mockRepositories([buildRepository()]);
244
+ mockFileContent(
245
+ 'const stripeKey = "sk_live_x";\nconst password = "hunter2000";',
246
+ );
247
+
248
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
249
+ { filePath: "src/config.ts" },
250
+ ctx,
251
+ );
252
+
253
+ expect(result.dataForLlm).not.toContain("hunter2000");
254
+ expect(result.dataForLlm).toContain("[redacted]");
255
+ expect(result.redactionCount).toBeGreaterThan(0);
256
+ });
257
+
258
+ test("an explicit endLine survives being combined with aroundLine", async () => {
259
+ mockRepositories([buildRepository()]);
260
+ mockFileContent(file);
261
+
262
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
263
+ { filePath: "src/billing/charge.ts", aroundLine: 200, endLine: 210 },
264
+ ctx,
265
+ );
266
+
267
+ expect(result.dataForLlm).toContain("showing lines 140-210 of 500");
268
+ });
269
+
270
+ test("a trailing newline does not invent a phantom last line", async () => {
271
+ mockRepositories([buildRepository()]);
272
+ mockFileContent("alpha\nbravo\n");
273
+
274
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
275
+ { filePath: "src/two.ts" },
276
+ ctx,
277
+ );
278
+
279
+ expect(result.dataForLlm).toContain("showing lines 1-2 of 2");
280
+ expect(result.rowCount).toBe(2);
281
+ });
282
+
283
+ test("an empty file reads as empty, not as an error", async () => {
284
+ mockRepositories([buildRepository()]);
285
+ mockFileContent("");
286
+
287
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
288
+ { filePath: "src/empty.ts" },
289
+ ctx,
290
+ );
291
+
292
+ expect(result.rowCount).toBe(0);
293
+ expect(result.dataForLlm).toContain("this file is empty");
294
+ });
295
+
296
+ /*
297
+ * The bug this locks: the serializer hard-slices at its own 16KB payload cap.
298
+ * If the window is trimmed only by line count, the header advertises a range
299
+ * the payload does not contain, and the "read on" hint then skips exactly the
300
+ * lines the slice ate.
301
+ */
302
+ test("reports the range it actually returned when lines are long", async () => {
303
+ mockRepositories([buildRepository()]);
304
+ // 400 lines x ~200 bytes = ~80KB, far over the byte budget.
305
+ const fat: string = Array.from(
306
+ { length: 400 },
307
+ (_v: unknown, i: number) => {
308
+ return `${i + 1}:${"x".repeat(200)}`;
309
+ },
310
+ ).join("\n");
311
+ mockFileContent(fat);
312
+
313
+ const result: ToolExecutionResult = await ReadCodeFileTool.execute(
314
+ { filePath: "src/fat.ts", startLine: 1, endLine: 400 },
315
+ ctx,
316
+ );
317
+
318
+ expect(result.isTruncated).toBe(true);
319
+
320
+ // The advertised range must match what is really in the payload.
321
+ const header: RegExpMatchArray | null = result.dataForLlm.match(
322
+ /showing lines 1-(\d+) of 400/,
323
+ );
324
+ expect(header).not.toBeNull();
325
+ const advertisedEnd: number = Number(header![1]);
326
+ expect(advertisedEnd).toBeLessThan(400);
327
+ expect(result.rowCount).toBe(advertisedEnd);
328
+ expect(result.dataForLlm).toContain(`${advertisedEnd}| `);
329
+ expect(result.dataForLlm).not.toContain(`${advertisedEnd + 1}| `);
330
+
331
+ // The pagination hint must resume exactly where it stopped — no skipped lines.
332
+ expect(result.dataForLlm).toContain(`startLine=${advertisedEnd + 1}`);
333
+ });
334
+ });
335
+
336
+ describe("search_code", () => {
337
+ test("returns shortest paths first so the likely file is not cut off", async () => {
338
+ mockRepositories([buildRepository()]);
339
+ jest
340
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
341
+ .mockResolvedValue([
342
+ "src/billing/__tests__/charge.fixture.ts",
343
+ "src/billing/charge.ts",
344
+ "docs/unrelated.md",
345
+ ]);
346
+
347
+ const result: ToolExecutionResult = await SearchCodeTool.execute(
348
+ { query: "charge" },
349
+ ctx,
350
+ );
351
+
352
+ const firstIndex: number = result.dataForLlm.indexOf(
353
+ "src/billing/charge.ts",
354
+ );
355
+ const fixtureIndex: number = result.dataForLlm.indexOf("charge.fixture.ts");
356
+
357
+ expect(firstIndex).toBeGreaterThanOrEqual(0);
358
+ expect(firstIndex).toBeLessThan(fixtureIndex);
359
+ expect(result.dataForLlm).not.toContain("docs/unrelated.md");
360
+ });
361
+
362
+ test("reports no matches honestly", async () => {
363
+ mockRepositories([buildRepository()]);
364
+ jest
365
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
366
+ .mockResolvedValue(["src/billing/charge.ts"]);
367
+
368
+ const result: ToolExecutionResult = await SearchCodeTool.execute(
369
+ { query: "nonexistent" },
370
+ ctx,
371
+ );
372
+
373
+ expect(result.rowCount).toBe(0);
374
+ expect(result.dataForLlm).toContain("no file paths");
375
+ });
376
+ });
377
+
378
+ describe("find_code_for_exception", () => {
379
+ function mockException(
380
+ stackTrace: string | undefined,
381
+ message?: string,
382
+ ): void {
383
+ const exception: TelemetryException = new TelemetryException();
384
+ exception._id = ObjectID.generate().toString();
385
+ exception.message = message ?? "Cannot read property 'id' of undefined";
386
+ exception.exceptionType = "TypeError";
387
+ exception.occuranceCount = 42;
388
+
389
+ if (stackTrace !== undefined) {
390
+ exception.stackTrace = stackTrace;
391
+ }
392
+
393
+ jest
394
+ .spyOn(TelemetryExceptionService, "findOneById")
395
+ .mockResolvedValue(exception as never);
396
+ }
397
+
398
+ function mockResolution(): void {
399
+ jest
400
+ .spyOn(CodeRepositoryService, "resolveRepositoryForException")
401
+ .mockResolvedValue({
402
+ codeRepositoryId: REPO_ID.toString(),
403
+ organizationName: "acme",
404
+ repositoryName: "checkout",
405
+ servicePathInRepository: null,
406
+ method: "stack-trace",
407
+ evidence: "Matched src/billing/charge.ts in acme/checkout",
408
+ });
409
+ }
410
+
411
+ test("maps a stack trace to a repository and its implicated frames", async () => {
412
+ mockException(
413
+ [
414
+ "TypeError: Cannot read property 'id' of undefined",
415
+ " at chargeCustomer (/app/src/billing/charge.ts:42:15)",
416
+ " at processPayment (/app/node_modules/express/lib/router.js:10:5)",
417
+ ].join("\n"),
418
+ );
419
+
420
+ mockRepositories([buildRepository()]);
421
+ mockResolution();
422
+ jest
423
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
424
+ .mockResolvedValue(["src/billing/charge.ts", "src/index.ts"]);
425
+
426
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
427
+ { exceptionId: ObjectID.generate().toString() },
428
+ ctx,
429
+ );
430
+
431
+ expect(result.dataForLlm).toContain("acme/checkout");
432
+ expect(result.dataForLlm).toContain(REPO_ID.toString());
433
+ expect(result.dataForLlm).toContain("stack-trace");
434
+ expect(result.dataForLlm).toContain("line=42");
435
+ // Library frames are dropped when application frames exist.
436
+ expect(result.dataForLlm).not.toContain("router.js");
437
+ });
438
+
439
+ /*
440
+ * The bug this locks: StackTraceParser reports the RUNTIME path
441
+ * (/app/src/billing/charge.ts), but the Contents API needs the REPOSITORY
442
+ * path (src/billing/charge.ts). Emitting the runtime path made every
443
+ * read_code_file follow-up 404 — the feature's whole point.
444
+ */
445
+ test("rewrites runtime stack-trace paths to real repository paths", async () => {
446
+ mockException(" at chargeCustomer (/app/src/billing/charge.ts:42:15)");
447
+ mockRepositories([buildRepository()]);
448
+ mockResolution();
449
+ jest
450
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
451
+ .mockResolvedValue(["src/billing/charge.ts"]);
452
+
453
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
454
+ { exceptionId: ObjectID.generate().toString() },
455
+ ctx,
456
+ );
457
+
458
+ expect(result.dataForLlm).toContain("file=src/billing/charge.ts");
459
+ expect(result.dataForLlm).not.toContain("/app/src/billing/charge.ts");
460
+ expect(result.dataForLlm).toContain("openableWithReadCodeFile=true");
461
+ });
462
+
463
+ test("marks a frame unopenable rather than guessing when it is not in the tree", async () => {
464
+ mockException(" at helper (/app/src/mystery/ghost.ts:7:1)");
465
+ mockRepositories([buildRepository()]);
466
+ mockResolution();
467
+ jest
468
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
469
+ .mockResolvedValue(["src/billing/charge.ts"]);
470
+
471
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
472
+ { exceptionId: ObjectID.generate().toString() },
473
+ ctx,
474
+ );
475
+
476
+ expect(result.dataForLlm).not.toContain("openableWithReadCodeFile=true");
477
+ expect(result.dataForLlm).toContain(
478
+ "None of these frames could be matched",
479
+ );
480
+ });
481
+
482
+ test("refuses to pick between two equally-plausible tree matches", async () => {
483
+ mockException(" at handler (/app/charge.ts:9:1)");
484
+ mockRepositories([buildRepository()]);
485
+ mockResolution();
486
+ jest
487
+ .spyOn(GitHubUtil, "getRepositoryTreePaths")
488
+ .mockResolvedValue(["src/billing/charge.ts", "src/legacy/charge.ts"]);
489
+
490
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
491
+ { exceptionId: ObjectID.generate().toString() },
492
+ ctx,
493
+ );
494
+
495
+ expect(result.dataForLlm).not.toContain("openableWithReadCodeFile=true");
496
+ });
497
+
498
+ /*
499
+ * exception.message is populated verbatim from the monitored app's thrown
500
+ * error — the most attacker-influenceable string in the tool belt. It must
501
+ * get the same redaction top_exceptions applies to the identical field.
502
+ */
503
+ test("redacts secrets in the exception message before they egress", async () => {
504
+ mockException(
505
+ " at auth (/app/src/auth.ts:1:1)",
506
+ "auth failed for admin@acme.com password=hunter2xyz key AKIAIOSFODNN7EXAMPLE",
507
+ );
508
+ mockRepositories([buildRepository()]);
509
+ mockResolution();
510
+ jest.spyOn(GitHubUtil, "getRepositoryTreePaths").mockResolvedValue([]);
511
+
512
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
513
+ { exceptionId: ObjectID.generate().toString() },
514
+ ctx,
515
+ );
516
+
517
+ expect(result.dataForLlm).not.toContain("admin@acme.com");
518
+ expect(result.dataForLlm).not.toContain("hunter2xyz");
519
+ expect(result.dataForLlm).not.toContain("AKIAIOSFODNN7EXAMPLE");
520
+ expect(result.redactionCount).toBeGreaterThan(0);
521
+ });
522
+
523
+ test("redacts the exception message on the no-stack-trace path too", async () => {
524
+ mockException(undefined, "boom for admin@acme.com");
525
+
526
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
527
+ { exceptionId: ObjectID.generate().toString() },
528
+ ctx,
529
+ );
530
+
531
+ expect(result.dataForLlm).not.toContain("admin@acme.com");
532
+ expect(result.redactionCount).toBeGreaterThan(0);
533
+ });
534
+
535
+ /*
536
+ * resolveRepositoryForException queries as isRoot (it serves the autonomous
537
+ * agent). A label-restricted user must not learn a repository's identity
538
+ * through it.
539
+ */
540
+ test("does not disclose a repository the user cannot read", async () => {
541
+ mockException(" at chargeCustomer (/app/src/billing/charge.ts:42:15)");
542
+ mockResolution();
543
+ // The user's own readable set is empty — the label ACL excludes this repo.
544
+ mockRepositories([]);
545
+
546
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
547
+ { exceptionId: ObjectID.generate().toString() },
548
+ ctx,
549
+ );
550
+
551
+ expect(result.dataForLlm).toContain("resolvedRepository: none");
552
+ expect(result.dataForLlm).not.toContain("acme/checkout");
553
+ expect(result.dataForLlm).not.toContain(REPO_ID.toString());
554
+ });
555
+
556
+ test("says the code could not be located rather than guessing a repository", async () => {
557
+ mockException(" at chargeCustomer (/app/src/billing/charge.ts:42:15)");
558
+
559
+ jest
560
+ .spyOn(CodeRepositoryService, "resolveRepositoryForException")
561
+ .mockResolvedValue(null);
562
+
563
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
564
+ { exceptionId: ObjectID.generate().toString() },
565
+ ctx,
566
+ );
567
+
568
+ expect(result.dataForLlm).toContain("resolvedRepository: none");
569
+ expect(result.dataForLlm).toContain("rather than guessing");
570
+ });
571
+
572
+ test("an exception with no stack trace is reported honestly", async () => {
573
+ mockException(undefined);
574
+
575
+ const result: ToolExecutionResult = await FindCodeForExceptionTool.execute(
576
+ { exceptionId: ObjectID.generate().toString() },
577
+ ctx,
578
+ );
579
+
580
+ expect(result.rowCount).toBe(0);
581
+ expect(result.dataForLlm).toContain("no stack trace recorded");
582
+ });
583
+
584
+ test("a missing exception is an error the model can act on", async () => {
585
+ jest
586
+ .spyOn(TelemetryExceptionService, "findOneById")
587
+ .mockResolvedValue(null as never);
588
+
589
+ await expect(
590
+ FindCodeForExceptionTool.execute(
591
+ { exceptionId: ObjectID.generate().toString() },
592
+ ctx,
593
+ ),
594
+ ).rejects.toThrow(/No exception found/);
595
+ });
596
+ });