Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ const AgentStudioPage = () => {
gatewayUrl,
draftGatewayUrl,
token,
allowSelfSignedCerts,
localGatewayDefaults,
localGatewayDefaultsHasToken,
hasStoredToken,
Expand All @@ -237,6 +238,7 @@ const AgentStudioPage = () => {
useLocalGatewayDefaults,
setGatewayUrl,
setToken,
setAllowSelfSignedCerts,
applyRuntimeStatusEvent,
} = useStudioGatewaySettings(settingsCoordinator);
const gatewayStatus: GatewayStatus = status;
Expand Down Expand Up @@ -1424,6 +1426,7 @@ const AgentStudioPage = () => {
savedGatewayUrl={gatewayUrl}
draftGatewayUrl={draftGatewayUrl}
token={token}
allowSelfSignedCerts={allowSelfSignedCerts}
localGatewayDefaults={localGatewayDefaults}
localGatewayDefaultsHasToken={localGatewayDefaultsHasToken}
hasStoredToken={hasStoredToken}
Expand All @@ -1438,6 +1441,7 @@ const AgentStudioPage = () => {
disconnecting={gatewayDisconnecting}
onGatewayUrlChange={setGatewayUrl}
onTokenChange={setToken}
onAllowSelfSignedCertsChange={setAllowSelfSignedCerts}
onUseLocalDefaults={useLocalGatewayDefaults}
onSaveSettings={() => void saveSettings()}
onTestConnection={() => void testConnection()}
Expand Down Expand Up @@ -1491,6 +1495,7 @@ const AgentStudioPage = () => {
savedGatewayUrl={gatewayUrl}
draftGatewayUrl={draftGatewayUrl}
token={token}
allowSelfSignedCerts={allowSelfSignedCerts}
hasStoredToken={hasStoredToken}
localGatewayDefaultsHasToken={localGatewayDefaultsHasToken}
hasUnsavedChanges={hasUnsavedChanges}
Expand All @@ -1503,6 +1508,7 @@ const AgentStudioPage = () => {
disconnecting={gatewayDisconnecting}
onGatewayUrlChange={setGatewayUrl}
onTokenChange={setToken}
onAllowSelfSignedCertsChange={setAllowSelfSignedCerts}
onSaveSettings={() => void saveSettings()}
onTestConnection={() => void testConnection()}
onDisconnect={() => void disconnect()}
Expand Down
27 changes: 27 additions & 0 deletions src/features/agents/components/ConnectionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type ConnectionPanelProps = {
savedGatewayUrl: string;
draftGatewayUrl: string;
token: string;
allowSelfSignedCerts: boolean;
hasStoredToken: boolean;
localGatewayDefaultsHasToken: boolean;
hasUnsavedChanges: boolean;
Expand All @@ -23,6 +24,7 @@ type ConnectionPanelProps = {
disconnecting: boolean;
onGatewayUrlChange: (value: string) => void;
onTokenChange: (value: string) => void;
onAllowSelfSignedCertsChange: (value: boolean) => void;
onSaveSettings: () => void;
onTestConnection: () => void;
onDisconnect: () => void;
Expand All @@ -33,6 +35,7 @@ export const ConnectionPanel = ({
savedGatewayUrl,
draftGatewayUrl,
token,
allowSelfSignedCerts,
hasStoredToken,
localGatewayDefaultsHasToken,
hasUnsavedChanges,
Expand All @@ -45,6 +48,7 @@ export const ConnectionPanel = ({
disconnecting,
onGatewayUrlChange,
onTokenChange,
onAllowSelfSignedCertsChange,
onSaveSettings,
onTestConnection,
onDisconnect,
Expand Down Expand Up @@ -131,6 +135,29 @@ export const ConnectionPanel = ({
/>
</label>
</div>

{/* Allow self-signed certificates checkbox */}
<div className="flex items-center gap-2">
<label className="flex cursor-pointer items-center gap-2 text-xs text-foreground/80">
<input
type="checkbox"
checked={allowSelfSignedCerts}
onChange={(event) => onAllowSelfSignedCertsChange(event.target.checked)}
className="h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary/30 focus:ring-offset-0"
/>
Allow self-signed certificates
</label>
</div>

{allowSelfSignedCerts && (
<div className="ui-alert-danger rounded-md px-3 py-2 text-xs">
<div className="flex items-center gap-1.5">
<span className="text-sm">⚠️</span>
<span>Self-signed certificates are enabled. Do not use in production.</span>
</div>
</div>
)}

<p className="text-xs text-muted-foreground">{tokenHelper}</p>
{hasUnsavedChanges ? (
<p className="font-mono text-[10px] font-semibold tracking-[0.06em] text-muted-foreground">
Expand Down
26 changes: 26 additions & 0 deletions src/features/agents/components/GatewayConnectScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type GatewayConnectScreenProps = {
savedGatewayUrl: string;
draftGatewayUrl: string;
token: string;
allowSelfSignedCerts: boolean;
localGatewayDefaults: StudioGatewaySettings | null;
localGatewayDefaultsHasToken: boolean;
hasStoredToken: boolean;
Expand All @@ -35,6 +36,7 @@ type GatewayConnectScreenProps = {
disconnecting: boolean;
onGatewayUrlChange: (value: string) => void;
onTokenChange: (value: string) => void;
onAllowSelfSignedCertsChange: (value: boolean) => void;
onUseLocalDefaults: () => void;
onSaveSettings: () => void;
onTestConnection: () => void;
Expand All @@ -54,6 +56,7 @@ export const GatewayConnectScreen = ({
savedGatewayUrl,
draftGatewayUrl,
token,
allowSelfSignedCerts,
localGatewayDefaults,
localGatewayDefaultsHasToken,
hasStoredToken,
Expand All @@ -68,6 +71,7 @@ export const GatewayConnectScreen = ({
disconnecting,
onGatewayUrlChange,
onTokenChange,
onAllowSelfSignedCertsChange,
onUseLocalDefaults,
onSaveSettings,
onTestConnection,
Expand Down Expand Up @@ -308,6 +312,28 @@ export const GatewayConnectScreen = ({

<p className="mt-2 text-xs leading-snug text-muted-foreground">{tokenHelper}</p>

{/* Allow self-signed certificates checkbox */}
<div className="mt-3 flex items-center gap-2">
<label className="flex cursor-pointer items-center gap-2 text-xs text-foreground/80">
<input
type="checkbox"
checked={allowSelfSignedCerts}
onChange={(event) => onAllowSelfSignedCertsChange(event.target.checked)}
className="h-4 w-4 rounded border-border text-primary focus:ring-2 focus:ring-primary/30 focus:ring-offset-0"
/>
Allow self-signed certificates
</label>
</div>

{allowSelfSignedCerts && (
<div className="ui-alert-danger mt-2 rounded-md px-3 py-2 text-xs">
<div className="flex items-center gap-1.5">
<span className="text-sm">⚠️</span>
<span>Self-signed certificates are enabled. Do not use in production.</span>
</div>
</div>
)}

{hasUnsavedChanges ? (
<p className="mt-2 font-mono text-[10px] font-semibold tracking-[0.06em] text-muted-foreground">
Unsaved changes
Expand Down
1 change: 1 addition & 0 deletions src/lib/controlplane/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type GatewayResponseFrame = {
export type ControlPlaneGatewaySettings = {
url: string;
token: string;
allowSelfSignedCerts?: boolean;
};

export type ControlPlaneRuntimeSnapshot = {
Expand Down
16 changes: 14 additions & 2 deletions src/lib/controlplane/gateway-connect-profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type GatewayConnectProfileId = "backend-local" | "legacy-control-ui";

export type GatewaySocketOptions = {
origin?: string;
rejectUnauthorized?: boolean;
};

export type GatewayConnectProfile = {
Expand Down Expand Up @@ -74,6 +75,7 @@ export function buildGatewayConnectProfile(args: {
token: string;
protocol: number;
capabilities: string[];
rejectUnauthorized?: boolean;
}): GatewayConnectProfile {
const baseParams = {
minProtocol: args.protocol,
Expand All @@ -84,10 +86,20 @@ export function buildGatewayConnectProfile(args: {
auth: { token: args.token },
};

const socketOptions: GatewaySocketOptions = {};

if (args.profileId === "legacy-control-ui") {
socketOptions.origin = resolveOriginForUpstream(args.upstreamUrl);
}

if (args.rejectUnauthorized === false) {
socketOptions.rejectUnauthorized = false;
}

if (args.profileId === "legacy-control-ui") {
return {
id: args.profileId,
socketOptions: { origin: resolveOriginForUpstream(args.upstreamUrl) },
socketOptions,
connectParams: {
...baseParams,
client: {
Expand All @@ -102,7 +114,7 @@ export function buildGatewayConnectProfile(args: {

return {
id: args.profileId,
socketOptions: {},
socketOptions,
connectParams: {
...baseParams,
client: {
Expand Down
15 changes: 13 additions & 2 deletions src/lib/controlplane/openclaw-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,14 @@ const loadGatewaySettings = (): ControlPlaneGatewaySettings => {
const gateway = settings.gateway;
const url = typeof gateway?.url === "string" ? gateway.url.trim() : "";
const token = typeof gateway?.token === "string" ? gateway.token.trim() : "";
const allowSelfSignedCerts = gateway?.allowSelfSignedCerts === true;
if (!url) {
throw new Error("Control-plane start failed: Studio gateway URL is not configured.");
}
if (!token) {
throw new Error("Control-plane start failed: Studio gateway token is not configured.");
}
return { url, token };
return { url, token, allowSelfSignedCerts };
};

export type OpenClawAdapterOptions = {
Expand Down Expand Up @@ -211,7 +212,16 @@ export class OpenClawGatewayAdapter {

constructor(options?: OpenClawAdapterOptions) {
this.loadSettings = options?.loadSettings ?? loadGatewaySettings;
this.createWebSocket = options?.createWebSocket ?? ((url, opts) => new WebSocket(url, opts));
this.createWebSocket = options?.createWebSocket ?? ((url, opts) => {
const wsOptions: any = {};
if (opts.origin) {
wsOptions.origin = opts.origin;
}
if (opts.rejectUnauthorized === false) {
wsOptions.rejectUnauthorized = false;
}
return new WebSocket(url, wsOptions);
});
this.methodAllowlist = options?.methodAllowlist ?? DEFAULT_METHOD_ALLOWLIST;
this.onDomainEvent = options?.onDomainEvent;
}
Expand Down Expand Up @@ -325,6 +335,7 @@ export class OpenClawGatewayAdapter {
token: settings.token,
protocol: CONNECT_PROTOCOL,
capabilities: CONNECT_CAPABILITIES,
rejectUnauthorized: settings.allowSelfSignedCerts ? false : undefined,
Comment on lines 335 to +338
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconnect live adapters when TLS validation changes

This new flag is only consumed when connect() builds the websocket profile, but src/app/api/studio/route.ts:32-47 still defines a gateway-settings change as url/token only before deciding whether to call reconnectForGatewaySettingsChange(). If the runtime is already connected, toggling the checkbox and saving persists the setting but leaves the existing websocket running with its previous TLS policy until some later restart, so the live connection never picks up the new option.

Useful? React with 👍 / 👎.

});
this.connectionEpoch = randomUUID();
const ws = this.createWebSocket(settings.url, profile.socketOptions);
Expand Down
10 changes: 8 additions & 2 deletions src/lib/studio/settings.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
export type StudioGatewaySettings = {
url: string;
token: string;
allowSelfSignedCerts?: boolean;
};

type StudioGatewaySettingsPatch = {
url?: string | null;
token?: string | null;
allowSelfSignedCerts?: boolean | null;
};

type FocusFilter = "all" | "running" | "approvals";
Expand Down Expand Up @@ -128,7 +130,8 @@ const normalizeGatewaySettings = (value: unknown): StudioGatewaySettings | null
const url = normalizeGatewayUrl(value.url);
if (!url) return null;
const token = coerceString(value.token);
return { url, token };
const allowSelfSignedCerts = typeof value.allowSelfSignedCerts === "boolean" ? value.allowSelfSignedCerts : false;
return { url, token, allowSelfSignedCerts };
};

const hasOwn = (value: Record<string, unknown>, key: string) =>
Expand All @@ -144,8 +147,11 @@ const mergeGatewaySettings = (

const nextUrl = hasOwn(patch, "url") ? normalizeGatewayUrl(patch.url) : current?.url ?? "";
const nextToken = hasOwn(patch, "token") ? coerceString(patch.token) : current?.token ?? "";
const nextAllowSelfSignedCerts = hasOwn(patch, "allowSelfSignedCerts") ?
(typeof patch.allowSelfSignedCerts === "boolean" ? patch.allowSelfSignedCerts : false) :
current?.allowSelfSignedCerts ?? false;
if (!nextUrl) return null;
return { url: nextUrl, token: nextToken };
return { url: nextUrl, token: nextToken, allowSelfSignedCerts: nextAllowSelfSignedCerts };
};

const normalizeFocused = (value: unknown): Record<string, StudioFocusedPreference> => {
Expand Down
Loading