@skyhook-io/radar-app 1.9.0 → 1.9.1
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 +27 -9
- package/src/api/client.ts +21 -6
- package/src/api/config.test.ts +47 -0
- package/src/api/config.ts +15 -0
- package/src/api/diagnose.ts +10 -15
- package/src/components/diagnose/AISettings.tsx +21 -7
- package/src/components/diagnose/DiagnoseContext.tsx +82 -53
- package/src/components/diagnose/DiagnoseSurface.tsx +13 -8
- package/src/components/diagnose/parts.test.tsx +125 -0
- package/src/components/diagnose/parts.tsx +166 -75
- package/src/components/resources/ResourcesView.tsx +9 -8
- package/src/components/settings/SettingsDialog.tsx +31 -19
- package/src/components/timeline/TimelineView.tsx +17 -3
- package/src/context/DiagnoseCustomization.tsx +1 -1
|
@@ -28,25 +28,29 @@ import { InvestigationView } from "./InvestigationView";
|
|
|
28
28
|
import { RecentList } from "./Home";
|
|
29
29
|
import { ConsentCard } from "./parts";
|
|
30
30
|
import { buildLaunchCommand, launchAgentLabel, openInTerminal } from "./launch";
|
|
31
|
-
import { type RunSummary } from "../../api/diagnose";
|
|
31
|
+
import { type RunSummary, type ExecutionProfile } from "../../api/diagnose";
|
|
32
32
|
|
|
33
33
|
function capWord(s: string): string {
|
|
34
34
|
return s ? s[0].toUpperCase() + s.slice(1) : s;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// buildConfigLine renders the active AI config as the header subtitle. Codex shows
|
|
38
|
-
// its
|
|
38
|
+
// its execution profile + effective reasoning effort (Default → medium); a model
|
|
39
39
|
// override is shown for either agent. Reflects a run's recorded settings, or the
|
|
40
40
|
// current defaults on Home.
|
|
41
41
|
function buildConfigLine(cfg: {
|
|
42
42
|
agent?: string;
|
|
43
|
-
|
|
43
|
+
profile?: ExecutionProfile;
|
|
44
44
|
model?: string;
|
|
45
45
|
effort?: string;
|
|
46
46
|
}): string {
|
|
47
47
|
const parts = [agentLabelFor(cfg.agent ?? "")];
|
|
48
|
+
if (cfg.profile) {
|
|
49
|
+
parts.push(
|
|
50
|
+
cfg.profile === "full-local" ? "Your agent setup" : "Radar safeguards",
|
|
51
|
+
);
|
|
52
|
+
}
|
|
48
53
|
if (cfg.agent === "codex") {
|
|
49
|
-
parts.push(cfg.isolated === false ? "My setup" : "Isolated");
|
|
50
54
|
parts.push(`${capWord(cfg.effort || "medium")} effort`);
|
|
51
55
|
}
|
|
52
56
|
if (cfg.model) parts.push(capWord(cfg.model));
|
|
@@ -139,7 +143,8 @@ export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
|
|
|
139
143
|
const d = useDiagnose();
|
|
140
144
|
// Injected settings action: undefined = Radar's own Settings dialog;
|
|
141
145
|
// null = hide the gear + links.
|
|
142
|
-
const { consentCopy, onOpenSettings: hostOpenSettings } =
|
|
146
|
+
const { consentCopy, onOpenSettings: hostOpenSettings } =
|
|
147
|
+
useDiagnoseCustomization();
|
|
143
148
|
const openSettings =
|
|
144
149
|
hostOpenSettings === undefined ? openDiagnoseSettings : hostOpenSettings;
|
|
145
150
|
const {
|
|
@@ -180,12 +185,12 @@ export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
|
|
|
180
185
|
? agentLabelFor(activeRun.agent)
|
|
181
186
|
: d.agentLabel;
|
|
182
187
|
// Header subtitle: the config a focused run actually used (it records agent /
|
|
183
|
-
//
|
|
188
|
+
// profile / model / effort), or the current defaults on Home. Codex shows mode
|
|
184
189
|
// + reasoning effort; model is shown only when overridden. Clicking opens Settings.
|
|
185
190
|
const configLine = buildConfigLine(
|
|
186
191
|
activeRun ?? {
|
|
187
192
|
agent: d.selectedAgent,
|
|
188
|
-
|
|
193
|
+
profile: d.hosted ? undefined : d.profile,
|
|
189
194
|
model: d.model,
|
|
190
195
|
effort: d.effort,
|
|
191
196
|
},
|
|
@@ -208,7 +213,7 @@ export function DiagnoseSurface({ topInset = 0 }: { topInset?: number }) {
|
|
|
208
213
|
<ConsentCard
|
|
209
214
|
agentName={d.agentLabel}
|
|
210
215
|
agent={d.selectedAgent}
|
|
211
|
-
|
|
216
|
+
profile={d.profile}
|
|
212
217
|
copy={consentCopy}
|
|
213
218
|
onOpenSettings={openSettings ?? undefined}
|
|
214
219
|
onApprove={d.approveConsent}
|
|
@@ -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
|
}
|
|
@@ -4,7 +4,7 @@ import { useQuery } from '@tanstack/react-query'
|
|
|
4
4
|
import { ApiError, debugNamespaceLog, fetchJSON, isForbiddenError, useCapabilities, useNamespaceCapabilities, useSecretCertExpiry, useTopPodMetrics, useTopNodeMetrics, useBulkDeleteResources, useBulkRestartWorkloads, useBulkScaleWorkloads, useAudit } from '../../api/client'
|
|
5
5
|
import { isBadgeWorthy } from '../../utils/auditBadges'
|
|
6
6
|
import type { AuditBadgeMessage } from '@skyhook-io/k8s-ui'
|
|
7
|
-
import { apiUrl, getAuthHeaders, getCredentialsMode,
|
|
7
|
+
import { apiUrl, getAuthHeaders, getCredentialsMode, stripBasename } from '../../api/config'
|
|
8
8
|
import { useAPIResources } from '../../api/apiResources'
|
|
9
9
|
import { useConnection } from '../../context/ConnectionContext'
|
|
10
10
|
import { initNavigationMap } from '@skyhook-io/k8s-ui'
|
|
@@ -198,7 +198,13 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
198
198
|
const podCountAllowsBulkMetrics = countsData != null && podCountKnown && !podCountUnavailable && (podCount ?? 0) <= LARGE_RESOURCE_LIST_LIMIT
|
|
199
199
|
const selectedKindName = selectedKind?.name.toLowerCase() ?? ''
|
|
200
200
|
const topPodMetricsEnabled = selectedKindName === 'pods' && podCountAllowsBulkMetrics
|
|
201
|
-
|
|
201
|
+
// Node metrics back the Nodes table and, for the Pods table, the pod-vs-node
|
|
202
|
+
// context line in the CPU/Memory tooltip (a pod can be fine against its own
|
|
203
|
+
// limit yet at risk from a saturated node). Nodes are cluster-wide, so the
|
|
204
|
+
// pods case is not gated on the namespace filter.
|
|
205
|
+
const topNodeMetricsEnabled =
|
|
206
|
+
((selectedKindName === 'nodes' && namespaces.length === 0) || selectedKindName === 'pods') &&
|
|
207
|
+
podCountAllowsBulkMetrics
|
|
202
208
|
const largeListGuard = selectedKind && largeListBlocked
|
|
203
209
|
? {
|
|
204
210
|
kind: selectedKind.name,
|
|
@@ -295,13 +301,8 @@ export function ResourcesView({ namespaces, selectedResource, onResourceClick, o
|
|
|
295
301
|
// any host that mounts RadarApp under a non-empty basename (Radar Cloud).
|
|
296
302
|
// Strip the basename here so react-router can re-apply it cleanly.
|
|
297
303
|
const handleNavigate = useMemo(() => {
|
|
298
|
-
const base = getBasename()
|
|
299
304
|
return (path: string, options?: { replace?: boolean }) => {
|
|
300
|
-
|
|
301
|
-
if (base && (p === base || p.startsWith(base + '/') || p.startsWith(base + '?'))) {
|
|
302
|
-
p = p.slice(base.length) || '/'
|
|
303
|
-
}
|
|
304
|
-
navigate(p, { replace: options?.replace })
|
|
305
|
+
navigate(stripBasename(path), { replace: options?.replace })
|
|
305
306
|
}
|
|
306
307
|
}, [navigate])
|
|
307
308
|
|