@skyhook-io/radar-app 1.9.0 → 1.9.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.
- package/package.json +7 -7
- package/src/App.tsx +69 -16
- package/src/api/apiResources.test.ts +11 -0
- package/src/api/apiResources.ts +51 -12
- package/src/api/client.capacity.test.ts +92 -0
- package/src/api/client.ts +2905 -2081
- package/src/api/config.test.ts +47 -0
- package/src/api/config.ts +15 -0
- package/src/api/diagnose.ts +15 -15
- package/src/components/ConnectionErrorView.test.tsx +88 -0
- package/src/components/ConnectionErrorView.tsx +128 -22
- package/src/components/capacity/CapacityActivity.tsx +787 -0
- package/src/components/capacity/CapacityDemand.tsx +961 -0
- package/src/components/capacity/CapacityOverview.tsx +1529 -0
- package/src/components/capacity/CapacityPoolDetail.tsx +1626 -0
- package/src/components/capacity/CapacityView.test.tsx +2287 -0
- package/src/components/capacity/CapacityView.tsx +85 -0
- package/src/components/capacity/ClusterSchedulingCard.tsx +603 -0
- package/src/components/capacity/DemandNomination.test.tsx +151 -0
- package/src/components/capacity/certaintyGlyph.test.tsx +191 -0
- package/src/components/capacity/coverageCertainty.test.ts +162 -0
- package/src/components/capacity/podDemandGate.test.ts +47 -0
- package/src/components/capacity/podDemandGate.ts +22 -0
- package/src/components/capacity/schedulingBar.test.ts +244 -0
- package/src/components/capacity/shared.tsx +1841 -0
- package/src/components/diagnose/AISettings.tsx +21 -7
- package/src/components/diagnose/AgentSetupNotice.tsx +117 -0
- package/src/components/diagnose/DiagnoseContext.tsx +127 -57
- package/src/components/diagnose/DiagnoseSurface.tsx +33 -15
- package/src/components/diagnose/LocalDiagnoseAction.tsx +50 -27
- package/src/components/diagnose/agentCatalog.ts +30 -0
- package/src/components/diagnose/parts.test.tsx +125 -0
- package/src/components/diagnose/parts.tsx +166 -75
- package/src/components/home/CapacityCard.test.tsx +150 -0
- package/src/components/home/CapacityCard.tsx +125 -0
- package/src/components/home/HomeView.tsx +15 -1
- package/src/components/issues/IssuesPane.test.ts +142 -0
- package/src/components/issues/IssuesPane.tsx +142 -38
- package/src/components/nav/PrimaryNavRail.test.tsx +20 -0
- package/src/components/nav/PrimaryNavRail.tsx +191 -103
- package/src/components/resources/ResourcesView.tsx +9 -8
- package/src/components/resources/renderers/KarpenterNodePoolRenderer.tsx +29 -1
- package/src/components/resources/renderers/PodRenderer.tsx +32 -3
- package/src/components/settings/SettingsDialog.tsx +31 -19
- package/src/components/timeline/TimelineView.tsx +17 -3
- package/src/components/ui/command-items.ts +222 -98
- package/src/components/workload/WorkloadView.tsx +16 -83
- package/src/context/ConnectionContext.test.ts +39 -0
- package/src/context/ConnectionContext.tsx +155 -51
- package/src/context/DiagnoseCustomization.tsx +1 -1
- package/src/utils/shell-safe.test.ts +55 -0
- package/src/utils/shell-safe.ts +21 -0
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { Loader2, Sparkles } from "lucide-react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
useDiagnose,
|
|
4
|
+
useDiagnoseLayout,
|
|
5
|
+
runTargetKey,
|
|
6
|
+
} from "./DiagnoseContext";
|
|
3
7
|
import { Tooltip } from "../ui/Tooltip";
|
|
4
8
|
import type { RenderDiagnoseAction } from "../../context/DiagnoseCustomization";
|
|
5
9
|
|
|
@@ -27,31 +31,41 @@ function DiagnoseResourceButton({
|
|
|
27
31
|
}) {
|
|
28
32
|
const d = useDiagnose();
|
|
29
33
|
const { runningKeys } = useDiagnoseLayout();
|
|
30
|
-
|
|
34
|
+
// Hidden only when the feature can't work here (auth/cloud/--no-mcp). When it's
|
|
35
|
+
// supported but no agent is installed we KEEP the button — clicking opens the
|
|
36
|
+
// setup notice so the feature is discoverable rather than silently absent.
|
|
37
|
+
if (d.setupState === "off") return null;
|
|
38
|
+
const ready = d.available;
|
|
31
39
|
const problem = health === "problem";
|
|
32
|
-
const running = runningKeys.has(runTargetKey(kind, namespace, name));
|
|
33
|
-
const tooltip =
|
|
34
|
-
?
|
|
35
|
-
:
|
|
36
|
-
?
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
:
|
|
40
|
+
const running = ready && runningKeys.has(runTargetKey(kind, namespace, name));
|
|
41
|
+
const tooltip = !ready
|
|
42
|
+
? "Set up AI diagnosis to investigate this — runs your own agent locally."
|
|
43
|
+
: running
|
|
44
|
+
? `${d.agentLabel} is investigating this resource — click to watch it live.`
|
|
45
|
+
: d.hosted
|
|
46
|
+
? problem
|
|
47
|
+
? `Diagnose with ${d.agentLabel} — reads this resource's spec, events & logs to find the root cause.`
|
|
48
|
+
: `Ask ${d.agentLabel} about this resource — reads its spec, events & logs.`
|
|
49
|
+
: problem
|
|
50
|
+
? `Diagnose with your own ${d.agentLabel} — runs locally, reads this resource's spec, events & logs to find the root cause.`
|
|
51
|
+
: `Ask your own ${d.agentLabel} about this resource — runs locally, reads its spec, events & logs.`;
|
|
42
52
|
// While an investigation is live, the button advertises it (and clicking focuses
|
|
43
53
|
// the existing run rather than starting a new one — openInvestigation dedups).
|
|
44
54
|
const showLabel = problem || running;
|
|
45
55
|
return (
|
|
46
56
|
<Tooltip content={tooltip} position="bottom">
|
|
47
57
|
<button
|
|
48
|
-
onClick={() =>
|
|
58
|
+
onClick={() =>
|
|
59
|
+
ready ? d.openInvestigation({ kind, namespace, name }) : d.openHome()
|
|
60
|
+
}
|
|
49
61
|
aria-label={
|
|
50
|
-
|
|
51
|
-
? "
|
|
52
|
-
:
|
|
53
|
-
? "
|
|
54
|
-
:
|
|
62
|
+
!ready
|
|
63
|
+
? "Set up AI diagnosis"
|
|
64
|
+
: running
|
|
65
|
+
? "Investigation running — click to view"
|
|
66
|
+
: problem
|
|
67
|
+
? "Diagnose with AI"
|
|
68
|
+
: "Ask AI about this resource"
|
|
55
69
|
}
|
|
56
70
|
className={
|
|
57
71
|
showLabel
|
|
@@ -97,20 +111,26 @@ export function IssueDiagnoseButton({
|
|
|
97
111
|
name: string;
|
|
98
112
|
}) {
|
|
99
113
|
const d = useDiagnose();
|
|
100
|
-
if (
|
|
114
|
+
if (d.setupState === "off") return null;
|
|
115
|
+
const ready = d.available;
|
|
101
116
|
return (
|
|
102
117
|
<Tooltip
|
|
103
118
|
content={
|
|
104
|
-
d.
|
|
105
|
-
?
|
|
106
|
-
:
|
|
119
|
+
d.setupState === "needs-restart"
|
|
120
|
+
? "Restart Radar to enable AI diagnosis — a supported agent is installed"
|
|
121
|
+
: !ready
|
|
122
|
+
? "Set up AI diagnosis — install a local agent to investigate"
|
|
123
|
+
: d.hosted
|
|
124
|
+
? `Sends this resource's context to ${d.agentLabel} to find the root cause`
|
|
125
|
+
: `Runs ${d.agentLabel} on your machine and sends it this resource's context to find the root cause`
|
|
107
126
|
}
|
|
108
127
|
position="left"
|
|
109
128
|
>
|
|
110
129
|
<button
|
|
111
130
|
onClick={(e) => {
|
|
112
131
|
e.stopPropagation();
|
|
113
|
-
d.openInvestigation({ kind, namespace, name });
|
|
132
|
+
if (ready) d.openInvestigation({ kind, namespace, name });
|
|
133
|
+
else d.openHome();
|
|
114
134
|
}}
|
|
115
135
|
className="flex shrink-0 items-center gap-1 rounded-md border border-theme-border px-2 py-1 text-xs text-theme-text-secondary hover:bg-theme-hover hover:text-theme-text-primary"
|
|
116
136
|
>
|
|
@@ -126,7 +146,8 @@ export function IssueDiagnoseButton({
|
|
|
126
146
|
export function GlobalDiagnoseButton() {
|
|
127
147
|
const d = useDiagnose();
|
|
128
148
|
const { runningKeys } = useDiagnoseLayout();
|
|
129
|
-
if (
|
|
149
|
+
if (d.setupState === "off") return null;
|
|
150
|
+
const ready = d.available;
|
|
130
151
|
const runningCount = runningKeys.size;
|
|
131
152
|
const agentSuffix = d.hosted
|
|
132
153
|
? `powered by ${d.agentLabel}`
|
|
@@ -134,9 +155,11 @@ export function GlobalDiagnoseButton() {
|
|
|
134
155
|
return (
|
|
135
156
|
<Tooltip
|
|
136
157
|
content={
|
|
137
|
-
|
|
138
|
-
?
|
|
139
|
-
:
|
|
158
|
+
!ready
|
|
159
|
+
? "Set up AI investigations — runs your own agent locally"
|
|
160
|
+
: runningCount > 0
|
|
161
|
+
? `${runningCount} investigation${runningCount > 1 ? "s" : ""} running — ${agentSuffix}`
|
|
162
|
+
: `AI investigations — ${agentSuffix}`
|
|
140
163
|
}
|
|
141
164
|
position="bottom"
|
|
142
165
|
>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Install metadata for the agent CLIs Radar can drive. Used by the setup notice
|
|
2
|
+
// shown when AI diagnosis is eligible but no agent is installed. Names match the
|
|
3
|
+
// backend's supported set (see internal/ai/detect.go knownAgents + isSupportedAgent).
|
|
4
|
+
export interface AgentInstall {
|
|
5
|
+
name: string; // stable id, matches AgentInfo.name
|
|
6
|
+
label: string; // display name
|
|
7
|
+
install: string; // one-line install command
|
|
8
|
+
docs: string; // canonical setup/docs URL
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SUPPORTED_AGENTS: AgentInstall[] = [
|
|
12
|
+
{
|
|
13
|
+
name: "claude",
|
|
14
|
+
label: "Claude Code",
|
|
15
|
+
install: "npm install -g @anthropic-ai/claude-code",
|
|
16
|
+
docs: "https://docs.claude.com/en/docs/claude-code/setup",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
name: "codex",
|
|
20
|
+
label: "Codex",
|
|
21
|
+
install: "npm install -g @openai/codex",
|
|
22
|
+
docs: "https://github.com/openai/codex",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "cursor-agent",
|
|
26
|
+
label: "Cursor CLI",
|
|
27
|
+
install: "curl https://cursor.com/install -fsS | bash",
|
|
28
|
+
docs: "https://docs.cursor.com/en/cli/overview",
|
|
29
|
+
},
|
|
30
|
+
];
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { AgentControls, ConsentCard } from "./parts";
|
|
4
|
+
import type { AgentInfo, ExecutionProfile } from "../../api/diagnose";
|
|
5
|
+
|
|
6
|
+
const noop = vi.fn();
|
|
7
|
+
|
|
8
|
+
function renderAgent(
|
|
9
|
+
agent: string,
|
|
10
|
+
profiles: ExecutionProfile[],
|
|
11
|
+
profile: ExecutionProfile,
|
|
12
|
+
) {
|
|
13
|
+
const agents: AgentInfo[] = [
|
|
14
|
+
{
|
|
15
|
+
name: agent,
|
|
16
|
+
label: agent,
|
|
17
|
+
path: agent,
|
|
18
|
+
version: "",
|
|
19
|
+
present: true,
|
|
20
|
+
supported: true,
|
|
21
|
+
profiles,
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
return renderToStaticMarkup(
|
|
25
|
+
<AgentControls
|
|
26
|
+
agents={agents}
|
|
27
|
+
selectedAgent={agent}
|
|
28
|
+
onSelectAgent={noop}
|
|
29
|
+
profile={profile}
|
|
30
|
+
onSetProfile={noop}
|
|
31
|
+
model=""
|
|
32
|
+
onSetModel={noop}
|
|
33
|
+
effort=""
|
|
34
|
+
onSetEffort={noop}
|
|
35
|
+
/>,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("AgentControls execution profile explanation", () => {
|
|
40
|
+
it("shows Cursor's fixed full-local warning even when the stored profile is stale", () => {
|
|
41
|
+
const html = renderAgent("cursor-agent", ["full-local"], "safeguarded");
|
|
42
|
+
expect(html).toContain("must use this agent");
|
|
43
|
+
expect(html).toContain("normal setup");
|
|
44
|
+
expect(html).toContain("always loads your global MCP servers");
|
|
45
|
+
expect(html).toContain("still enables the agent CLI");
|
|
46
|
+
expect(html).toContain("does not constrain external MCP servers");
|
|
47
|
+
expect(html).not.toContain("always runs this agent with safeguards");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("discloses the Claude configuration that Radar cannot exclude", () => {
|
|
51
|
+
const html = renderAgent(
|
|
52
|
+
"claude",
|
|
53
|
+
["safeguarded", "full-local"],
|
|
54
|
+
"safeguarded",
|
|
55
|
+
);
|
|
56
|
+
expect(html).toContain("built-in tools are disabled");
|
|
57
|
+
expect(html).toContain("settings, hooks, and CLAUDE.md instructions still apply");
|
|
58
|
+
expect(html).toContain("Your claude setup");
|
|
59
|
+
expect(html).not.toContain("other agent configuration is excluded");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("explains that Claude full-local inherits the user's permissions", () => {
|
|
63
|
+
const html = renderAgent(
|
|
64
|
+
"claude",
|
|
65
|
+
["safeguarded", "full-local"],
|
|
66
|
+
"full-local",
|
|
67
|
+
);
|
|
68
|
+
expect(html).toContain("permissions from your setup");
|
|
69
|
+
expect(html).toContain("Radar does not override them");
|
|
70
|
+
expect(html).not.toContain("Radar still enables the agent CLI");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("does not describe an unknown safeguarded agent as Codex", () => {
|
|
74
|
+
const html = renderAgent("future-agent", ["safeguarded"], "safeguarded");
|
|
75
|
+
expect(html).toContain("documented restrictions");
|
|
76
|
+
expect(html).not.toContain("Codex");
|
|
77
|
+
expect(html).not.toContain("built-in tools are disabled");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("labels the selectable full-local profile as the user's agent setup", () => {
|
|
81
|
+
const html = renderAgent(
|
|
82
|
+
"codex",
|
|
83
|
+
["safeguarded", "full-local"],
|
|
84
|
+
"safeguarded",
|
|
85
|
+
);
|
|
86
|
+
expect(html).toContain("Your codex setup");
|
|
87
|
+
expect(html).not.toContain("Full local setup");
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("ConsentCard execution profile treatment", () => {
|
|
92
|
+
it("uses warning chrome and explicit consequences for the user's agent setup", () => {
|
|
93
|
+
const html = renderToStaticMarkup(
|
|
94
|
+
<ConsentCard
|
|
95
|
+
agentName="Cursor Agent"
|
|
96
|
+
agent="cursor-agent"
|
|
97
|
+
profile="full-local"
|
|
98
|
+
onApprove={noop}
|
|
99
|
+
onCancel={noop}
|
|
100
|
+
/>,
|
|
101
|
+
);
|
|
102
|
+
expect(html).toContain("Run using your Cursor Agent setup?");
|
|
103
|
+
expect(html).toContain("Continue with my agent setup");
|
|
104
|
+
expect(html).toContain("border-amber-500/40");
|
|
105
|
+
expect(html).toContain("text-amber-500");
|
|
106
|
+
expect(html).toContain("Radar cannot constrain");
|
|
107
|
+
expect(html).toContain("always loads your global MCP servers");
|
|
108
|
+
expect(html).not.toContain("text-accent");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("does not claim Radar enables a sandbox for Claude's normal setup", () => {
|
|
112
|
+
const html = renderToStaticMarkup(
|
|
113
|
+
<ConsentCard
|
|
114
|
+
agentName="Claude Code"
|
|
115
|
+
agent="claude"
|
|
116
|
+
profile="full-local"
|
|
117
|
+
onApprove={noop}
|
|
118
|
+
onCancel={noop}
|
|
119
|
+
/>,
|
|
120
|
+
);
|
|
121
|
+
expect(html).toContain("permissions from your setup");
|
|
122
|
+
expect(html).toContain("Radar does not override them");
|
|
123
|
+
expect(html).not.toContain("Radar still enables the agent CLI");
|
|
124
|
+
});
|
|
125
|
+
});
|
|
@@ -27,13 +27,14 @@ import {
|
|
|
27
27
|
type Diagnosis,
|
|
28
28
|
type DiagnoseStep,
|
|
29
29
|
type AgentInfo,
|
|
30
|
+
type ExecutionProfile,
|
|
30
31
|
type RunSummary,
|
|
31
32
|
} from "../../api/diagnose";
|
|
32
33
|
import { StatusDot } from "@skyhook-io/k8s-ui";
|
|
33
34
|
import { Markdown } from "../ui/Markdown";
|
|
34
35
|
|
|
35
|
-
// Segmented two-or-more-way selector — shared shape for the agent
|
|
36
|
-
//
|
|
36
|
+
// Segmented two-or-more-way selector — shared shape for the agent and execution
|
|
37
|
+
// profile pickers.
|
|
37
38
|
function Segmented<T extends string | boolean>({
|
|
38
39
|
label,
|
|
39
40
|
options,
|
|
@@ -228,15 +229,15 @@ function SelectMenu({
|
|
|
228
229
|
);
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
// AgentControls is the full AI-diagnosis config block (agent,
|
|
232
|
+
// AgentControls is the full AI-diagnosis config block (agent, execution profile, model,
|
|
232
233
|
// effort) — pure + prop-driven. It lives in Settings, not the investigation panel,
|
|
233
234
|
// since these are set-once preferences rather than per-run knobs.
|
|
234
235
|
export function AgentControls({
|
|
235
236
|
agents,
|
|
236
237
|
selectedAgent,
|
|
237
238
|
onSelectAgent,
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
profile,
|
|
240
|
+
onSetProfile,
|
|
240
241
|
model,
|
|
241
242
|
onSetModel,
|
|
242
243
|
effort,
|
|
@@ -245,8 +246,8 @@ export function AgentControls({
|
|
|
245
246
|
agents: AgentInfo[];
|
|
246
247
|
selectedAgent: string;
|
|
247
248
|
onSelectAgent: (name: string) => void;
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
profile: ExecutionProfile;
|
|
250
|
+
onSetProfile: (v: ExecutionProfile) => void;
|
|
250
251
|
model: string;
|
|
251
252
|
onSetModel: (v: string) => void;
|
|
252
253
|
effort: string;
|
|
@@ -255,6 +256,17 @@ export function AgentControls({
|
|
|
255
256
|
const isCodex = selectedAgent === "codex";
|
|
256
257
|
const isClaude = selectedAgent === "claude";
|
|
257
258
|
const isCursor = selectedAgent === "cursor-agent";
|
|
259
|
+
const selectedAgentInfo = agents.find((a) => a.name === selectedAgent);
|
|
260
|
+
const selectedAgentLabel =
|
|
261
|
+
selectedAgentInfo?.label || selectedAgent || "agent";
|
|
262
|
+
const profiles = selectedAgentInfo?.profiles ?? [];
|
|
263
|
+
const shownProfile = profiles.includes(profile)
|
|
264
|
+
? profile
|
|
265
|
+
: (profiles[0] ?? profile);
|
|
266
|
+
const profileLabels: Record<ExecutionProfile, string> = {
|
|
267
|
+
safeguarded: "Radar safeguards",
|
|
268
|
+
"full-local": `Your ${selectedAgentLabel} setup`,
|
|
269
|
+
};
|
|
258
270
|
return (
|
|
259
271
|
<div className="space-y-3">
|
|
260
272
|
{agents.length >= 2 && (
|
|
@@ -268,29 +280,66 @@ export function AgentControls({
|
|
|
268
280
|
}))}
|
|
269
281
|
/>
|
|
270
282
|
)}
|
|
271
|
-
{
|
|
283
|
+
{profiles.length > 0 && (
|
|
272
284
|
<div>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
285
|
+
{profiles.length > 1 ? (
|
|
286
|
+
<>
|
|
287
|
+
<Segmented<ExecutionProfile>
|
|
288
|
+
label="How Radar runs it"
|
|
289
|
+
value={shownProfile}
|
|
290
|
+
onChange={onSetProfile}
|
|
291
|
+
options={profiles.map((value) => ({
|
|
292
|
+
value,
|
|
293
|
+
label: profileLabels[value],
|
|
294
|
+
}))}
|
|
295
|
+
/>
|
|
296
|
+
{shownProfile === "safeguarded" ? (
|
|
297
|
+
<p className="mt-1.5 text-[11px] leading-snug text-theme-text-tertiary">
|
|
298
|
+
{isClaude
|
|
299
|
+
? "Claude’s built-in tools are disabled, and MCP access is limited to Radar’s read-only investigation tools. Your Claude settings, hooks, and CLAUDE.md instructions still apply and are outside Radar’s control."
|
|
300
|
+
: isCodex
|
|
301
|
+
? "Radar excludes your Codex configuration and other MCP servers. Codex’s sandboxed shell can still read files on this machine; it cannot write or reach the network."
|
|
302
|
+
: "Radar uses this agent’s safeguarded execution profile. Review the agent’s documented restrictions before continuing."}
|
|
303
|
+
</p>
|
|
304
|
+
) : (
|
|
305
|
+
<div className="mt-1.5 flex items-start gap-1.5 rounded border border-amber-500/40 bg-amber-500/10 p-2 text-[11px] leading-snug text-theme-text-secondary">
|
|
306
|
+
<AlertTriangle className="mt-0.5 h-3 w-3 shrink-0 text-amber-500" />
|
|
307
|
+
<span>
|
|
308
|
+
Uses your agent's normal configuration and other
|
|
309
|
+
configured tools and MCP servers. Radar cannot constrain that
|
|
310
|
+
external tooling; it may access local files or the network
|
|
311
|
+
and may be able to change your cluster.{" "}
|
|
312
|
+
{isClaude
|
|
313
|
+
? "Claude uses the permissions from your setup; Radar does not override them."
|
|
314
|
+
: "Radar still enables the agent CLI’s own sandbox, but that sandbox does not constrain external MCP servers."}{" "}
|
|
315
|
+
Choose this only when you need that setup.
|
|
316
|
+
</span>
|
|
317
|
+
</div>
|
|
318
|
+
)}
|
|
319
|
+
</>
|
|
320
|
+
) : shownProfile === "safeguarded" ? (
|
|
321
|
+
<div className="flex items-start gap-1.5 rounded border border-theme-border bg-theme-base p-2 text-[11px] leading-snug text-theme-text-secondary">
|
|
322
|
+
<ShieldCheck className="mt-0.5 h-3 w-3 shrink-0 text-accent" />
|
|
323
|
+
<span>
|
|
324
|
+
Radar always runs this agent with safeguards.
|
|
325
|
+
{isClaude
|
|
326
|
+
? " Claude’s built-in tools are disabled, and MCP access is limited to Radar’s read-only investigation tools. Your Claude settings, hooks, and CLAUDE.md instructions still apply and are outside Radar’s control."
|
|
327
|
+
: isCodex
|
|
328
|
+
? " Your Codex configuration and other MCP servers are excluded. Codex’s sandboxed shell can still read files on this machine; it cannot write or reach the network."
|
|
329
|
+
: " Review the agent’s documented restrictions before continuing."}
|
|
330
|
+
</span>
|
|
331
|
+
</div>
|
|
287
332
|
) : (
|
|
288
|
-
<div className="
|
|
333
|
+
<div className="flex items-start gap-1.5 rounded border border-amber-500/40 bg-amber-500/10 p-2 text-[11px] leading-snug text-theme-text-secondary">
|
|
289
334
|
<AlertTriangle className="mt-0.5 h-3 w-3 shrink-0 text-amber-500" />
|
|
290
335
|
<span>
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
local files
|
|
336
|
+
Radar must use this agent's normal setup. Radar cannot
|
|
337
|
+
constrain its external tools or MCP servers; they may access
|
|
338
|
+
local files or the network and may be able to change your
|
|
339
|
+
cluster. Radar still enables the agent CLI's own sandbox,
|
|
340
|
+
but that sandbox does not constrain external MCP servers.
|
|
341
|
+
{isCursor &&
|
|
342
|
+
" Cursor always loads your global MCP servers, so Radar cannot exclude them."}
|
|
294
343
|
</span>
|
|
295
344
|
</div>
|
|
296
345
|
)}
|
|
@@ -304,7 +353,7 @@ export function AgentControls({
|
|
|
304
353
|
onChange={onSetModel}
|
|
305
354
|
hint="Aliases always resolve to the latest of that tier."
|
|
306
355
|
/>
|
|
307
|
-
) : (
|
|
356
|
+
) : isCodex || isCursor ? (
|
|
308
357
|
<TextField
|
|
309
358
|
label="Model"
|
|
310
359
|
value={model}
|
|
@@ -317,11 +366,19 @@ export function AgentControls({
|
|
|
317
366
|
hint={
|
|
318
367
|
isCursor
|
|
319
368
|
? "Leave empty for your Cursor default, or enter a model slug Cursor supports."
|
|
320
|
-
:
|
|
321
|
-
? "
|
|
369
|
+
: shownProfile === "full-local"
|
|
370
|
+
? "Your Codex setup uses its configured model; set a slug here to override it."
|
|
322
371
|
: "Leave empty for Codex's default, or enter a model your Codex version supports."
|
|
323
372
|
}
|
|
324
373
|
/>
|
|
374
|
+
) : (
|
|
375
|
+
<TextField
|
|
376
|
+
label="Model"
|
|
377
|
+
value={model}
|
|
378
|
+
placeholder="Default"
|
|
379
|
+
onChange={onSetModel}
|
|
380
|
+
hint="Leave empty for the agent's default, or enter a model identifier it supports."
|
|
381
|
+
/>
|
|
325
382
|
)}
|
|
326
383
|
{isCodex && (
|
|
327
384
|
<SelectMenu
|
|
@@ -591,10 +648,12 @@ function ConsentCardShell({
|
|
|
591
648
|
bullets,
|
|
592
649
|
settingsLabel,
|
|
593
650
|
approveLabel = "Approve & investigate",
|
|
651
|
+
warning = false,
|
|
594
652
|
onOpenSettings,
|
|
595
653
|
onApprove,
|
|
596
654
|
onCancel,
|
|
597
655
|
}: DiagnoseConsentCopy & {
|
|
656
|
+
warning?: boolean;
|
|
598
657
|
onOpenSettings?: () => void;
|
|
599
658
|
onApprove: () => void;
|
|
600
659
|
onCancel: () => void;
|
|
@@ -606,9 +665,19 @@ function ConsentCardShell({
|
|
|
606
665
|
? "Change the agent and how it runs in Settings"
|
|
607
666
|
: settingsLabel;
|
|
608
667
|
return (
|
|
609
|
-
<div
|
|
668
|
+
<div
|
|
669
|
+
className={
|
|
670
|
+
warning
|
|
671
|
+
? "rounded-lg border border-amber-500/40 bg-amber-500/10 p-4"
|
|
672
|
+
: "rounded-lg border border-theme-border bg-theme-elevated p-4"
|
|
673
|
+
}
|
|
674
|
+
>
|
|
610
675
|
<div className="mb-2 flex items-center gap-2">
|
|
611
|
-
|
|
676
|
+
{warning ? (
|
|
677
|
+
<AlertTriangle className="h-4 w-4 text-amber-500" />
|
|
678
|
+
) : (
|
|
679
|
+
<ShieldCheck className="h-4 w-4 text-accent" />
|
|
680
|
+
)}
|
|
612
681
|
<div className="text-sm font-medium text-theme-text-primary">
|
|
613
682
|
{title}
|
|
614
683
|
</div>
|
|
@@ -660,7 +729,7 @@ function ConsentCardShell({
|
|
|
660
729
|
export function ConsentCard({
|
|
661
730
|
agentName,
|
|
662
731
|
agent,
|
|
663
|
-
|
|
732
|
+
profile,
|
|
664
733
|
copy,
|
|
665
734
|
onOpenSettings,
|
|
666
735
|
onApprove,
|
|
@@ -668,7 +737,7 @@ export function ConsentCard({
|
|
|
668
737
|
}: {
|
|
669
738
|
agentName: string;
|
|
670
739
|
agent?: string;
|
|
671
|
-
|
|
740
|
+
profile: ExecutionProfile;
|
|
672
741
|
copy?: DiagnoseConsentCopy;
|
|
673
742
|
onOpenSettings?: () => void;
|
|
674
743
|
onApprove: () => void;
|
|
@@ -679,17 +748,19 @@ export function ConsentCard({
|
|
|
679
748
|
// Tier 1: a host (e.g. radar-hub-web) supplied its own copy — use it verbatim.
|
|
680
749
|
if (copy) return <ConsentCardShell {...copy} {...chrome} />;
|
|
681
750
|
|
|
682
|
-
// Tier 2: OSS BYO-local default. Cursor can't be isolated (no flag suppresses
|
|
683
|
-
// its global MCP servers), so it gets its own honest framing rather than the
|
|
684
|
-
// isolated/my-setup pair.
|
|
685
|
-
const isCursor = agent === "cursor-agent";
|
|
686
751
|
return (
|
|
687
752
|
<ConsentCardShell
|
|
688
753
|
{...chrome}
|
|
754
|
+
warning={profile === "full-local"}
|
|
755
|
+
approveLabel={
|
|
756
|
+
profile === "full-local"
|
|
757
|
+
? "Continue with my agent setup"
|
|
758
|
+
: "Approve & investigate"
|
|
759
|
+
}
|
|
689
760
|
title={
|
|
690
|
-
|
|
691
|
-
? "Run
|
|
692
|
-
:
|
|
761
|
+
profile === "safeguarded"
|
|
762
|
+
? "Run an AI investigation with Radar safeguards?"
|
|
763
|
+
: `Run using your ${agentName} setup?`
|
|
693
764
|
}
|
|
694
765
|
body={
|
|
695
766
|
<>
|
|
@@ -698,48 +769,68 @@ export function ConsentCard({
|
|
|
698
769
|
your own {agentName}
|
|
699
770
|
</span>{" "}
|
|
700
771
|
on your machine — no Radar cloud, no API key, no account. Radar sends
|
|
701
|
-
this resource's spec, recent events, and pod logs to it (and on
|
|
702
|
-
its model provider under your account, not to Radar). Transcripts
|
|
703
|
-
kept in your local Radar history on this machine until cleared.
|
|
704
|
-
{
|
|
772
|
+
this resource's spec, recent events, and pod logs to it (and on
|
|
773
|
+
to its model provider under your account, not to Radar). Transcripts
|
|
774
|
+
are kept in your local Radar history on this machine until cleared.
|
|
775
|
+
{profile === "safeguarded" && (
|
|
705
776
|
<>
|
|
706
777
|
{" "}
|
|
707
|
-
|
|
708
|
-
<span className="font-medium">read</span>
|
|
709
|
-
cluster.
|
|
778
|
+
Radar's investigation tools can only{" "}
|
|
779
|
+
<span className="font-medium">read</span> your cluster.
|
|
710
780
|
</>
|
|
711
781
|
)}
|
|
712
782
|
</>
|
|
713
783
|
}
|
|
714
|
-
bullets={
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
784
|
+
bullets={
|
|
785
|
+
profile === "safeguarded"
|
|
786
|
+
? [
|
|
787
|
+
agent === "claude" ? (
|
|
788
|
+
<>
|
|
789
|
+
Radar safeguards disable Claude's built-in tools and limit
|
|
790
|
+
MCP access to Radar's read-only investigation tools. Your
|
|
791
|
+
Claude settings, hooks, and CLAUDE.md instructions still apply
|
|
792
|
+
and are outside Radar's control.
|
|
793
|
+
</>
|
|
794
|
+
) : agent === "codex" ? (
|
|
795
|
+
<>
|
|
796
|
+
Radar safeguards exclude your Codex configuration and other MCP
|
|
797
|
+
servers. Codex's sandboxed shell can still read files on
|
|
798
|
+
this machine; it cannot write or reach the network.
|
|
799
|
+
</>
|
|
800
|
+
) : (
|
|
801
|
+
<>
|
|
802
|
+
Radar uses this agent's safeguarded execution profile.
|
|
803
|
+
Review the agent's documented restrictions before
|
|
804
|
+
continuing.
|
|
805
|
+
</>
|
|
806
|
+
),
|
|
807
|
+
]
|
|
808
|
+
: [
|
|
728
809
|
<>
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
810
|
+
Radar cannot constrain the agent's other configured tools or
|
|
811
|
+
MCP servers. They may access local files or the network and may
|
|
812
|
+
be able to change your cluster.
|
|
813
|
+
</>,
|
|
814
|
+
agent === "claude" ? (
|
|
815
|
+
<>
|
|
816
|
+
Claude uses the permissions from your setup; Radar does not
|
|
817
|
+
override them.
|
|
818
|
+
</>
|
|
819
|
+
) : (
|
|
820
|
+
<>
|
|
821
|
+
Radar still enables the agent CLI's own sandbox, but that
|
|
822
|
+
sandbox does not constrain external MCP servers.
|
|
823
|
+
{agent === "cursor-agent" && (
|
|
824
|
+
<>
|
|
825
|
+
{" "}
|
|
826
|
+
Cursor always loads your global MCP servers; Radar cannot
|
|
827
|
+
exclude them.
|
|
828
|
+
</>
|
|
829
|
+
)}
|
|
830
|
+
</>
|
|
831
|
+
),
|
|
832
|
+
]
|
|
833
|
+
}
|
|
743
834
|
/>
|
|
744
835
|
);
|
|
745
836
|
}
|