@skyhook-io/radar-app 1.13.0 → 1.13.2

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 (54) hide show
  1. package/package.json +2 -2
  2. package/src/App.tsx +55 -9
  3. package/src/RadarApp.tsx +3 -3
  4. package/src/api/diagnose.test.ts +268 -0
  5. package/src/api/diagnose.ts +117 -14
  6. package/src/components/diagnose/AISettings.tsx +10 -5
  7. package/src/components/diagnose/AgentSetupNotice.tsx +5 -5
  8. package/src/components/diagnose/ApplyDialog.test.tsx +72 -0
  9. package/src/components/diagnose/DiagnoseContext.tsx +228 -65
  10. package/src/components/diagnose/DiagnoseSurface.test.tsx +317 -9
  11. package/src/components/diagnose/DiagnoseSurface.tsx +621 -132
  12. package/src/components/diagnose/Home.test.tsx +293 -0
  13. package/src/components/diagnose/Home.tsx +307 -108
  14. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +2170 -0
  15. package/src/components/diagnose/InvestigationEvidencePane.tsx +2253 -0
  16. package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +257 -0
  17. package/src/components/diagnose/InvestigationResourceEvidence.tsx +214 -0
  18. package/src/components/diagnose/InvestigationView.test.ts +17 -0
  19. package/src/components/diagnose/InvestigationView.tsx +1911 -338
  20. package/src/components/diagnose/LocalDiagnoseAction.tsx +42 -25
  21. package/src/components/diagnose/agentCatalog.ts +1 -1
  22. package/src/components/diagnose/diagnoseEvidenceTypes.ts +151 -0
  23. package/src/components/diagnose/investigationEvidence.test.ts +3109 -0
  24. package/src/components/diagnose/investigationEvidence.ts +3492 -0
  25. package/src/components/diagnose/investigationEvidencePresentation.test.ts +447 -0
  26. package/src/components/diagnose/investigationEvidencePresentation.ts +167 -0
  27. package/src/components/diagnose/investigationExplanation.test.ts +63 -0
  28. package/src/components/diagnose/investigationExplanation.ts +22 -0
  29. package/src/components/diagnose/investigationResourceEvidenceModel.ts +322 -0
  30. package/src/components/diagnose/investigationSourceFocus.test.ts +143 -0
  31. package/src/components/diagnose/investigationSourceFocus.ts +98 -0
  32. package/src/components/diagnose/investigationState.test.ts +695 -0
  33. package/src/components/diagnose/investigationState.ts +451 -0
  34. package/src/components/diagnose/parts.test.tsx +864 -3
  35. package/src/components/diagnose/parts.tsx +1340 -536
  36. package/src/components/diagnose/target.test.ts +39 -0
  37. package/src/components/diagnose/target.ts +36 -0
  38. package/src/components/diagnose/useDisclosureReveal.ts +117 -0
  39. package/src/components/execution/BatchExecutionView.render.test.tsx +35 -0
  40. package/src/components/execution/BatchExecutionView.tsx +2 -3
  41. package/src/components/execution/execution-definition.test.ts +19 -0
  42. package/src/components/execution/execution-definition.ts +2 -0
  43. package/src/components/gitops/GitOpsView.tsx +25 -3
  44. package/src/components/home/MCPSetupDialog.tsx +2 -2
  45. package/src/components/home/mcpToolCatalog.test.ts +22 -0
  46. package/src/components/home/mcpToolCatalog.ts +3 -2
  47. package/src/components/issues/IssuesPane.tsx +5 -1
  48. package/src/components/nav/navigation.test.ts +26 -0
  49. package/src/components/nav/navigation.ts +10 -0
  50. package/src/components/settings/SettingsDialog.tsx +11 -13
  51. package/src/components/workload/WorkloadView.tsx +1 -1
  52. package/src/context/DiagnoseCustomization.tsx +11 -8
  53. package/src/index.css +63 -79
  54. package/src/index.ts +1 -1
@@ -1,5 +1,21 @@
1
- import { describe, expect, it } from "vitest";
2
- import { canStartNewInvestigation } from "./DiagnoseSurface";
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ DIAGNOSE_SURFACE_FRAME_CLASS,
4
+ MAXIMIZED_COMPACT_HISTORY_VISIBILITY_CLASS,
5
+ MAXIMIZED_HOME_DETAIL_VISIBILITY_CLASS,
6
+ MAXIMIZED_HOME_RUN_HEADER_VISIBILITY_CLASS,
7
+ INVESTIGATION_HISTORY_MIN_WIDTH,
8
+ MAXIMIZED_RUN_META_VISIBILITY_CLASS,
9
+ canStartNewInvestigation,
10
+ canCopyRunLink,
11
+ investigationHeaderPresentation,
12
+ openInvestigationEvidenceResource,
13
+ } from "./DiagnoseSurface";
14
+ import {
15
+ canContinueInvestigation,
16
+ canInvestigateFurther,
17
+ canStopInvestigation,
18
+ } from "./investigationState";
3
19
  import type { RunSummary } from "../../api/diagnose";
4
20
 
5
21
  // The "new investigation" button dispatches an agent and spends the user's own
@@ -9,6 +25,7 @@ function run(status: RunSummary["status"]): RunSummary {
9
25
  return {
10
26
  id: "r1",
11
27
  kind: "Deployment",
28
+ group: "apps",
12
29
  namespace: "prod",
13
30
  name: "payments",
14
31
  context: "prod-cluster",
@@ -34,20 +51,32 @@ describe("canStartNewInvestigation", () => {
34
51
 
35
52
  it("stays hidden while a turn is in flight", () => {
36
53
  // A start would be handed back the live run, so the button does nothing.
37
- expect(canStartNewInvestigation("investigation", run("running"), false)).toBe(
38
- false,
39
- );
54
+ expect(
55
+ canStartNewInvestigation("investigation", run("running"), false),
56
+ ).toBe(false);
40
57
  });
41
58
 
42
59
  it("stays hidden on a stale run", () => {
43
- // The body offers "Re-run on current cluster" WITH the context-changed
44
- // warning. A bare + carries none of it, at a resource that may not exist in
45
- // the context it would now run against.
60
+ // A closed session must not start a new investigation against a resource
61
+ // that may not exist in the active cluster.
46
62
  expect(canStartNewInvestigation("investigation", run("stale"), false)).toBe(
47
63
  false,
48
64
  );
49
65
  });
50
66
 
67
+ it("blocks fresh starts while a human turn stops, but allows a separate human run from an automatic one", () => {
68
+ expect(
69
+ canStartNewInvestigation("investigation", run("stopping"), false),
70
+ ).toBe(false);
71
+ expect(
72
+ canStartNewInvestigation(
73
+ "investigation",
74
+ { ...run("running"), trigger: "background" },
75
+ false,
76
+ ),
77
+ ).toBe(true);
78
+ });
79
+
51
80
  it("stays hidden while consent is pending", () => {
52
81
  expect(canStartNewInvestigation("investigation", run("done"), true)).toBe(
53
82
  false,
@@ -63,8 +92,287 @@ describe("canStartNewInvestigation", () => {
63
92
  expect(canStartNewInvestigation("investigation", run("error"), false)).toBe(
64
93
  true,
65
94
  );
66
- expect(canStartNewInvestigation("investigation", run("stopped"), false)).toBe(
95
+ expect(
96
+ canStartNewInvestigation("investigation", run("stopped"), false),
97
+ ).toBe(true);
98
+ });
99
+ });
100
+
101
+ describe("investigation history navigation", () => {
102
+ it("restores the docked surface before opening an evidence resource", () => {
103
+ const events: string[] = [];
104
+ const onOpenResource = vi.fn(() => events.push("open"));
105
+ const setMaximized = vi.fn(() => events.push("dock"));
106
+ const closeDiagnose = vi.fn(() => events.push("close"));
107
+ const ref = {
108
+ kind: "Deployment",
109
+ group: "apps",
110
+ namespace: "shop",
111
+ name: "api",
112
+ };
113
+
114
+ openInvestigationEvidenceResource(
115
+ ref,
116
+ onOpenResource,
117
+ setMaximized,
118
+ closeDiagnose,
119
+ false,
120
+ );
121
+
122
+ expect(events).toEqual(["dock", "open"]);
123
+ expect(setMaximized).toHaveBeenCalledWith(false);
124
+ expect(closeDiagnose).not.toHaveBeenCalled();
125
+ expect(onOpenResource).toHaveBeenCalledWith(ref);
126
+ });
127
+
128
+ it("closes an overlay before opening an evidence resource", () => {
129
+ const events: string[] = [];
130
+ const onOpenResource = vi.fn(() => events.push("open"));
131
+ const setMaximized = vi.fn(() => events.push("dock"));
132
+ const closeDiagnose = vi.fn(() => events.push("close"));
133
+ const ref = {
134
+ kind: "Pod",
135
+ namespace: "shop",
136
+ name: "api-7d9f",
137
+ };
138
+
139
+ openInvestigationEvidenceResource(
140
+ ref,
141
+ onOpenResource,
142
+ setMaximized,
143
+ closeDiagnose,
67
144
  true,
68
145
  );
146
+
147
+ expect(events).toEqual(["close", "open"]);
148
+ expect(setMaximized).not.toHaveBeenCalled();
149
+ expect(closeDiagnose).toHaveBeenCalledOnce();
150
+ expect(onOpenResource).toHaveBeenCalledWith(ref);
151
+ });
152
+
153
+ it("keeps document overflow out of the bounded Diagnose frame", () => {
154
+ expect(DIAGNOSE_SURFACE_FRAME_CLASS).toContain("absolute");
155
+ expect(DIAGNOSE_SURFACE_FRAME_CLASS).toContain("min-h-0");
156
+ expect(DIAGNOSE_SURFACE_FRAME_CLASS).toContain("overflow-hidden");
157
+ expect(DIAGNOSE_SURFACE_FRAME_CLASS).not.toContain("overflow-y-auto");
158
+ });
159
+
160
+ it("reserves the history rail for wider investigation surfaces", () => {
161
+ expect(INVESTIGATION_HISTORY_MIN_WIDTH).toBe(1750);
162
+ expect(MAXIMIZED_COMPACT_HISTORY_VISIBILITY_CLASS).toBe(
163
+ "@min-[1750px]/diagnose-surface:hidden",
164
+ );
165
+ expect(MAXIMIZED_HOME_DETAIL_VISIBILITY_CLASS).toBe(
166
+ "hidden @min-[1750px]/diagnose-surface:flex",
167
+ );
168
+ expect(MAXIMIZED_HOME_RUN_HEADER_VISIBILITY_CLASS).toBe(
169
+ "hidden @min-[1750px]/diagnose-surface:block",
170
+ );
171
+ expect(MAXIMIZED_RUN_META_VISIBILITY_CLASS).toBe(
172
+ "hidden @min-[1750px]/diagnose-surface:flex",
173
+ );
174
+ });
175
+
176
+ it("keeps docked Home generic and removes actions for its retained run", () => {
177
+ expect(
178
+ investigationHeaderPresentation({
179
+ view: "home",
180
+ maximized: false,
181
+ hasVisibleRunDetail: true,
182
+ }),
183
+ ).toEqual({
184
+ genericIdentityClass: "",
185
+ detailIdentityClass: null,
186
+ runActionsClass: null,
187
+ });
188
+ });
189
+
190
+ it("swaps generic Home identity for the labeled retained detail at the wide breakpoint", () => {
191
+ expect(
192
+ investigationHeaderPresentation({
193
+ view: "home",
194
+ maximized: true,
195
+ hasVisibleRunDetail: true,
196
+ }),
197
+ ).toEqual({
198
+ genericIdentityClass: MAXIMIZED_COMPACT_HISTORY_VISIBILITY_CLASS,
199
+ detailIdentityClass: MAXIMIZED_HOME_RUN_HEADER_VISIBILITY_CLASS,
200
+ runActionsClass: MAXIMIZED_HOME_DETAIL_VISIBILITY_CLASS,
201
+ });
202
+ });
203
+
204
+ it("labels a direct detail at every size and keeps its run actions with it", () => {
205
+ expect(
206
+ investigationHeaderPresentation({
207
+ view: "investigation",
208
+ maximized: false,
209
+ hasVisibleRunDetail: true,
210
+ }),
211
+ ).toEqual({
212
+ genericIdentityClass: null,
213
+ detailIdentityClass: "",
214
+ runActionsClass: "",
215
+ });
216
+ });
217
+
218
+ it("does not invent a retained-detail header or actions without a run", () => {
219
+ expect(
220
+ investigationHeaderPresentation({
221
+ view: "home",
222
+ maximized: true,
223
+ hasVisibleRunDetail: false,
224
+ }),
225
+ ).toEqual({
226
+ genericIdentityClass: "",
227
+ detailIdentityClass: null,
228
+ runActionsClass: null,
229
+ });
230
+ });
231
+
232
+ it("removes run actions when another surface replaces the direct detail", () => {
233
+ expect(
234
+ investigationHeaderPresentation({
235
+ view: "investigation",
236
+ maximized: true,
237
+ hasVisibleRunDetail: false,
238
+ }),
239
+ ).toEqual({
240
+ genericIdentityClass: null,
241
+ detailIdentityClass: "",
242
+ runActionsClass: null,
243
+ });
244
+ });
245
+ });
246
+
247
+ describe("canStopInvestigation", () => {
248
+ it("lets the terminal transcript outrank a lagging running summary", () => {
249
+ expect(canStopInvestigation(run("running"), false, false, "done")).toBe(
250
+ false,
251
+ );
252
+ expect(canStopInvestigation(run("running"), false, false, "error")).toBe(
253
+ false,
254
+ );
255
+ });
256
+
257
+ it("keeps Stop available for an active human turn", () => {
258
+ expect(canStopInvestigation(run("running"), true, false, "running")).toBe(
259
+ true,
260
+ );
261
+ });
262
+
263
+ it("does not offer another Stop while the server drains the turn", () => {
264
+ expect(canStopInvestigation(run("stopping"), true, false, "running")).toBe(
265
+ false,
266
+ );
267
+ });
268
+
269
+ it("never lets a missing or automatic run expose Stop", () => {
270
+ expect(canStopInvestigation(run("running"), true, true, "running")).toBe(
271
+ false,
272
+ );
273
+ expect(
274
+ canStopInvestigation(
275
+ { ...run("running"), trigger: "background" },
276
+ true,
277
+ false,
278
+ "running",
279
+ ),
280
+ ).toBe(false);
281
+ });
282
+ });
283
+
284
+ describe("canContinueInvestigation", () => {
285
+ it("lets a terminal transcript outrank only a lagging running human summary", () => {
286
+ expect(
287
+ canContinueInvestigation(
288
+ { ...run("running"), canContinue: false },
289
+ "done",
290
+ ),
291
+ ).toBe(true);
292
+ });
293
+
294
+ it("keeps genuinely read-only and sessionless investigations read-only", () => {
295
+ expect(
296
+ canContinueInvestigation(
297
+ {
298
+ ...run("running"),
299
+ trigger: "background",
300
+ canContinue: false,
301
+ },
302
+ "done",
303
+ ),
304
+ ).toBe(false);
305
+ expect(
306
+ canContinueInvestigation({ ...run("done"), canContinue: false }, "done"),
307
+ ).toBe(false);
308
+ });
309
+
310
+ it("does not offer a follow-up after the retained run disappears", () => {
311
+ expect(
312
+ canContinueInvestigation(
313
+ { ...run("running"), canContinue: false },
314
+ "error",
315
+ true,
316
+ ),
317
+ ).toBe(false);
318
+ });
319
+ });
320
+
321
+ describe("canCopyRunLink", () => {
322
+ it("does not expose collaboration UI for an OSS run", () => {
323
+ expect(canCopyRunLink(run("done"))).toBe(false);
324
+ });
325
+
326
+ it("exposes the copy action only for a canonical hosted URL", () => {
327
+ expect(
328
+ canCopyRunLink({
329
+ ...run("done"),
330
+ radarUrl: "/c/cluster-1?org=org-1&ai-run=r1",
331
+ }),
332
+ ).toBe(true);
333
+ });
334
+
335
+ it("treats an empty hosted URL as unavailable", () => {
336
+ expect(canCopyRunLink({ ...run("done"), radarUrl: "" })).toBe(false);
337
+ });
338
+ });
339
+
340
+ describe("canInvestigateFurther", () => {
341
+ it("offers a context-preserving next step on a completed automatic investigation", () => {
342
+ expect(
343
+ canInvestigateFurther({
344
+ ...run("done"),
345
+ trigger: "background",
346
+ issueId: "issue-1",
347
+ }),
348
+ ).toBe(true);
349
+ });
350
+
351
+ it("does not promise findings for missing, unfinished, stale or human investigations", () => {
352
+ expect(canInvestigateFurther(run("done"))).toBe(false);
353
+ expect(
354
+ canInvestigateFurther({ ...run("done"), trigger: "background" }),
355
+ ).toBe(false);
356
+ for (const status of [
357
+ "running",
358
+ "stopping",
359
+ "error",
360
+ "stopped",
361
+ "stale",
362
+ ] as const) {
363
+ expect(
364
+ canInvestigateFurther({
365
+ ...run(status),
366
+ trigger: "background",
367
+ issueId: "issue-1",
368
+ }),
369
+ ).toBe(false);
370
+ }
371
+ expect(
372
+ canInvestigateFurther(
373
+ { ...run("done"), trigger: "background", issueId: "issue-1" },
374
+ true,
375
+ ),
376
+ ).toBe(false);
69
377
  });
70
378
  });