@skyhook-io/radar-app 1.14.2 → 1.14.3

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 (74) hide show
  1. package/package.json +2 -2
  2. package/src/api/client.ts +30 -0
  3. package/src/components/CloudConnectFlow.tsx +21 -7
  4. package/src/components/CloudFunnelButton.tsx +85 -8
  5. package/src/components/cloudConnectHandoff.test.ts +10 -1
  6. package/src/components/cloudConnectHandoff.ts +15 -1
  7. package/src/components/diagnose/AIMarkdown.tsx +87 -0
  8. package/src/components/diagnose/ActivityTurn.tsx +1244 -0
  9. package/src/components/diagnose/AgentControls.tsx +603 -0
  10. package/src/components/diagnose/AnalysisStory.test.tsx +31 -25
  11. package/src/components/diagnose/AnalysisStory.tsx +13 -18
  12. package/src/components/diagnose/ApplyDialog.tsx +289 -0
  13. package/src/components/diagnose/AssessmentCard.tsx +1560 -0
  14. package/src/components/diagnose/DiagnoseSurface.test.tsx +9 -9
  15. package/src/components/diagnose/DiagnoseSurface.tsx +5 -1
  16. package/src/components/diagnose/EvidenceCard.tsx +611 -0
  17. package/src/components/diagnose/Home.test.tsx +1 -3
  18. package/src/components/diagnose/Home.tsx +2 -1
  19. package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +6 -8
  20. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +5 -3
  21. package/src/components/diagnose/InvestigationEvidencePane.tsx +31 -3445
  22. package/src/components/diagnose/InvestigationEvidenceSections.tsx +523 -0
  23. package/src/components/diagnose/InvestigationView.tsx +11 -28
  24. package/src/components/diagnose/StoryExcerpt.tsx +118 -0
  25. package/src/components/diagnose/assessmentCopy.ts +169 -0
  26. package/src/components/diagnose/investigationCase.test.tsx +631 -2
  27. package/src/components/diagnose/investigationCase.ts +132 -24
  28. package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +660 -0
  29. package/src/components/diagnose/investigationEvidence/adapters/{helm.test.ts → helm.test.tsx} +63 -0
  30. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +70 -1
  31. package/src/components/diagnose/investigationEvidence/adapters/listings.ts +134 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/{permissions.test.ts → permissions.test.tsx} +50 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +18 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/posture.ts +272 -0
  35. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +49 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/rankings.ts +147 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +20 -10
  38. package/src/components/diagnose/investigationEvidence/bodies/index.tsx +127 -0
  39. package/src/components/diagnose/investigationEvidence/bodies/inventory.tsx +158 -0
  40. package/src/components/diagnose/investigationEvidence/bodies/metrics.tsx +373 -0
  41. package/src/components/diagnose/investigationEvidence/bodies/network.tsx +274 -0
  42. package/src/components/diagnose/investigationEvidence/bodies/platform.tsx +475 -0
  43. package/src/components/diagnose/investigationEvidence/bodies/posture.tsx +60 -0
  44. package/src/components/diagnose/investigationEvidence/bodies/ranking.tsx +76 -0
  45. package/src/components/diagnose/investigationEvidence/bodies/streams.tsx +212 -0
  46. package/src/components/diagnose/investigationEvidence/bodies/workload.tsx +335 -0
  47. package/src/components/diagnose/investigationEvidence/builder.ts +15 -0
  48. package/src/components/diagnose/investigationEvidence/cardParts.tsx +312 -0
  49. package/src/components/diagnose/investigationEvidence/excerpt.test.ts +105 -0
  50. package/src/components/diagnose/investigationEvidence/excerpt.ts +92 -0
  51. package/src/components/diagnose/investigationEvidence/identity.test.ts +5 -7
  52. package/src/components/diagnose/investigationEvidence/index.ts +1 -1
  53. package/src/components/diagnose/investigationEvidence/navigation.tsx +26 -0
  54. package/src/components/diagnose/investigationEvidence/observations.ts +11 -0
  55. package/src/components/diagnose/investigationEvidence/projection.test.ts +1 -1
  56. package/src/components/diagnose/investigationEvidence/types.ts +97 -2
  57. package/src/components/diagnose/investigationEvidenceKinds.ts +22 -0
  58. package/src/components/diagnose/investigationEvidencePartition.ts +296 -0
  59. package/src/components/diagnose/parts.test.tsx +3 -1
  60. package/src/components/diagnose/parts.tsx +26 -3904
  61. package/src/components/diagnose/target.ts +9 -3
  62. package/src/components/diagnose/toolCallLabel.test.ts +47 -1
  63. package/src/components/diagnose/toolCallLabel.ts +41 -2
  64. package/src/components/helm/HelmReleaseDrawer.tsx +1 -1
  65. package/src/components/home/MCPSetupDialog.tsx +1 -1
  66. package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
  67. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  68. package/src/components/resource/WorkloadMetricsSection.tsx +1 -1
  69. package/src/components/resources/PodFilePreview.tsx +1 -1
  70. package/src/components/ui/CommandPalette.tsx +12 -8
  71. package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
  72. package/src/components/ui/Omnibar.tsx +33 -10
  73. package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
  74. package/src/components/ui/Disclosure.tsx +0 -47
@@ -0,0 +1,118 @@
1
+ import { clsx } from "clsx";
2
+ import {
3
+ Collapse,
4
+ TerminalBlock,
5
+ TerminalBlockLabel,
6
+ } from "@skyhook-io/k8s-ui";
7
+ import { InventoryRow } from "./investigationEvidence/bodies/inventory";
8
+ import { RankingRow } from "./investigationEvidence/bodies/ranking";
9
+ import { PostureFindingRow } from "./investigationEvidence/bodies/posture";
10
+ import { MetricsBody } from "./investigationEvidence/bodies/metrics";
11
+ import type { StoryExcerpt as Excerpt } from "./investigationEvidence/excerpt";
12
+ import type { InvestigationEvidenceData } from "./investigationEvidence";
13
+ import type { MetricsChangeCoverage } from "./investigationMetrics";
14
+
15
+ /** The inline slice of a placed card, rendered from Radar's record. */
16
+ export function StoryExcerpt({
17
+ excerpt,
18
+ data,
19
+ prominence,
20
+ open,
21
+ canExpand,
22
+ changeCoverage,
23
+ }: {
24
+ excerpt: Excerpt;
25
+ data: InvestigationEvidenceData;
26
+ prominence: "primary" | "supporting" | "secondary";
27
+ open: boolean;
28
+ canExpand: boolean;
29
+ changeCoverage?: MetricsChangeCoverage;
30
+ }) {
31
+ const padding = prominence === "primary" ? "px-3 pb-2.5" : "px-2.5 pb-2";
32
+ switch (excerpt.kind) {
33
+ case "lines":
34
+ return (
35
+ <div data-story-log-lines className={padding}>
36
+ <TerminalBlock
37
+ footer={
38
+ excerpt.rest.length > 0 ? (
39
+ <Collapse open={open && canExpand}>
40
+ <TerminalBlockLabel divider>Earlier lines</TerminalBlockLabel>
41
+ <pre className="overflow-x-auto whitespace-pre-wrap break-words px-3 pb-2.5 font-mono text-xs leading-relaxed text-[var(--terminal-text)]">
42
+ {excerpt.rest.join("\n")}
43
+ </pre>
44
+ </Collapse>
45
+ ) : null
46
+ }
47
+ >
48
+ {excerpt.head.join("\n")}
49
+ </TerminalBlock>
50
+ </div>
51
+ );
52
+ case "rows":
53
+ return (
54
+ <div data-story-inventory-rows className={padding}>
55
+ <div className="rounded-md border border-theme-border">
56
+ {excerpt.entries.map((entry, index) =>
57
+ entry.row ? (
58
+ <InventoryRow
59
+ key={entry.key}
60
+ resource={entry.row}
61
+ divider={index > 0}
62
+ />
63
+ ) : (
64
+ <p
65
+ key={entry.key}
66
+ data-story-inventory-absent={
67
+ entry.matches === 0 ? "" : undefined
68
+ }
69
+ className={clsx(
70
+ "px-2.5 py-1.5 font-mono text-xs text-theme-text-secondary",
71
+ index > 0 && "border-t border-theme-border/60",
72
+ )}
73
+ >
74
+ {entry.matches === 0
75
+ ? `Not in this listing: ${entry.label}`
76
+ : `${entry.label}: ${entry.matches} entries match in this listing`}
77
+ </p>
78
+ ),
79
+ )}
80
+ </div>
81
+ </div>
82
+ );
83
+ case "ranking":
84
+ return (
85
+ <div data-story-ranking-rows className={padding}>
86
+ <div className="rounded-md border border-theme-border">
87
+ {excerpt.rows.map((row, index) => (
88
+ <RankingRow
89
+ key={`${row.namespace ?? ""}/${row.name}`}
90
+ row={row}
91
+ divider={index > 0}
92
+ />
93
+ ))}
94
+ </div>
95
+ </div>
96
+ );
97
+ case "posture":
98
+ return (
99
+ <div data-story-posture-findings className={padding}>
100
+ <div className="rounded-md border border-theme-border">
101
+ {excerpt.findings.map((finding, index) => (
102
+ <PostureFindingRow
103
+ key={`${finding.name}-${finding.check}`}
104
+ finding={finding}
105
+ divider={index > 0}
106
+ />
107
+ ))}
108
+ </div>
109
+ </div>
110
+ );
111
+ case "chart":
112
+ return data.type === "metrics" ? (
113
+ <div data-story-metrics className={padding}>
114
+ <MetricsBody data={data} changeCoverage={changeCoverage} />
115
+ </div>
116
+ ) : null;
117
+ }
118
+ }
@@ -0,0 +1,169 @@
1
+ import type { Diagnosis } from "../../api/diagnose";
2
+ import { storyPlainText } from "./investigationStory";
3
+
4
+ /**
5
+ * The whole assessment as text: headline, certainty, what is unresolved, the
6
+ * technical cause, the story without its markers, and the steps. Copying only
7
+ * the headline would ship the persuasive half without its caveats.
8
+ */
9
+ /** What Radar adds to the agent's verdict when the assessment is copied for a channel. */
10
+ export interface AssessmentCopyRadar {
11
+ /** Reads Radar could not complete, as shown in Still open. */
12
+ limits?: readonly string[];
13
+ /** Adverse Radar cards the agent explained, as shown in Still open. */
14
+ signals?: readonly string[];
15
+ /** Adverse Radar cards nothing explains, as shown above a healthy verdict. */
16
+ flags?: readonly string[];
17
+ /** Where and when: the header line a reader in a channel needs first. */
18
+ context?: {
19
+ target: string;
20
+ cluster?: string;
21
+ startedAt?: string;
22
+ agent?: string;
23
+ };
24
+ /** The placed cards, as short excerpts: the receipts behind the headline. */
25
+ receipts?: readonly {
26
+ title: string;
27
+ role?: string;
28
+ lines: readonly string[];
29
+ /** The agent's statement of what this result does not cover. */
30
+ gap?: string;
31
+ }[];
32
+ }
33
+
34
+ /**
35
+ * The assessment as Markdown for a channel or a ticket: context, headline,
36
+ * cause, the receipts, what is still open, the next step. The full story and
37
+ * every captured result stay in Radar; the text says so.
38
+ */
39
+ export function assessmentCopyText(
40
+ diagnosis: Diagnosis,
41
+ radar: AssessmentCopyRadar = {},
42
+ ): string {
43
+ const parts: string[] = [];
44
+ if (radar.context) {
45
+ const when = radar.context.startedAt
46
+ ? new Date(radar.context.startedAt).toLocaleString(undefined, {
47
+ dateStyle: "medium",
48
+ timeStyle: "short",
49
+ })
50
+ : undefined;
51
+ parts.push(
52
+ [
53
+ `**${radar.context.target}**`,
54
+ radar.context.cluster,
55
+ when,
56
+ radar.context.agent ? `via ${radar.context.agent}` : undefined,
57
+ ]
58
+ .filter(Boolean)
59
+ .join(" · "),
60
+ );
61
+ }
62
+ if (diagnosis.summary) {
63
+ const certainty = diagnosis.healthy
64
+ ? "Healthy"
65
+ : diagnosis.certainty
66
+ ? CERTAINTY_LABEL[diagnosis.certainty]
67
+ : undefined;
68
+ parts.push(
69
+ `**${diagnosis.summary.trim()}**${certainty ? ` — ${certainty} (agent's word)` : ""}`,
70
+ );
71
+ } else if (diagnosis.certainty) {
72
+ parts.push(`Certainty (agent): ${CERTAINTY_LABEL[diagnosis.certainty]}`);
73
+ }
74
+ // The pasted text must not read more confident than the screen: Radar's
75
+ // own qualifications travel with the agent's.
76
+ const flags = (radar.flags ?? []).filter((item) => item.trim());
77
+ if (flags.length > 0)
78
+ parts.push(
79
+ [
80
+ "Radar flagged:",
81
+ ...flags.map((item) => `- ${item}`),
82
+ "Read those cards before treating this as an all-clear.",
83
+ ].join("\n"),
84
+ );
85
+ const unresolved = [
86
+ ...(diagnosis.unresolved ?? []),
87
+ ...(radar.limits ?? []),
88
+ ...(radar.signals ?? []),
89
+ ].filter((item) => item.trim());
90
+ if (unresolved.length > 0)
91
+ parts.push(
92
+ ["Still open:", ...unresolved.map((item) => `- ${item}`)].join("\n"),
93
+ );
94
+ if (diagnosis.rootCause) parts.push(storyPlainText(diagnosis.rootCause));
95
+ const receipts = (radar.receipts ?? []).filter((receipt) => receipt.title);
96
+ if (receipts.length > 0) {
97
+ parts.push(
98
+ [
99
+ "Evidence (Radar):",
100
+ ...receipts.map((receipt) =>
101
+ [
102
+ `- **${receipt.title}**${receipt.role ? ` · ${receipt.role}` : ""}`,
103
+ ...receipt.lines
104
+ .filter((line) => line.trim())
105
+ .map((line) => ` > ${line.trim()}`),
106
+ ...(receipt.gap ? [` Not shown: ${receipt.gap}`] : []),
107
+ ].join("\n"),
108
+ ),
109
+ ].join("\n"),
110
+ );
111
+ } else if (!radar.context) {
112
+ const story = storyPlainText(diagnosis.report ?? "");
113
+ if (story) parts.push(story);
114
+ }
115
+ const steps = diagnosis.steps?.length
116
+ ? diagnosis.steps.map(
117
+ (step, index) =>
118
+ `${index + 1}. [${STEP_KIND_LABEL[step.kind]}] ${step.text}${step.precondition ? ` (only if ${step.precondition})` : ""}`,
119
+ )
120
+ : (diagnosis.remediation ?? []).map(
121
+ (text, index) => `${index + 1}. ${text}`,
122
+ );
123
+ if (steps.length > 0) {
124
+ if (radar.context) {
125
+ // A channel gets the recommended step; the rest wait in Radar.
126
+ const lead = diagnosis.recommendedIndex
127
+ ? diagnosis.recommendedIndex - 1
128
+ : 0;
129
+ const first = steps[lead] ?? steps[0];
130
+ const rest = steps.length - 1;
131
+ parts.push(
132
+ [
133
+ `Next step: ${first.replace(/^\d+\. /, "")}`,
134
+ diagnosis.recommendedReason
135
+ ? `Why: ${diagnosis.recommendedReason}`
136
+ : undefined,
137
+ rest > 0
138
+ ? `${rest} more ${rest === 1 ? "step" : "steps"} in Radar.`
139
+ : undefined,
140
+ ]
141
+ .filter(Boolean)
142
+ .join("\n"),
143
+ );
144
+ } else parts.push(["Next steps:", ...steps].join("\n"));
145
+ }
146
+ if (radar.context)
147
+ parts.push(
148
+ "Full analysis and every captured result: open the investigation in Radar.",
149
+ );
150
+ return parts.join("\n\n");
151
+ }
152
+
153
+ export const CERTAINTY_LABEL: Record<
154
+ NonNullable<Diagnosis["certainty"]>,
155
+ string
156
+ > = {
157
+ established: "Established",
158
+ likely: "Likely",
159
+ suspected: "Suspected",
160
+ };
161
+
162
+ export const STEP_KIND_LABEL: Record<
163
+ NonNullable<Diagnosis["steps"]>[number]["kind"],
164
+ string
165
+ > = {
166
+ mitigate: "Mitigate",
167
+ verify: "Verify",
168
+ investigate: "Investigate",
169
+ };