mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 12:15:57 +00:00
feat(agy): add power-user bypass for responsibility acknowledgement
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
import { createInterface, Interface } from 'readline';
|
||||
import { fail, info, ok, warn } from '../utils/ui';
|
||||
import { getCliproxySafetyConfig } from '../config/unified-config-loader';
|
||||
|
||||
export const ANTIGRAVITY_RISK_ISSUE_URL = 'https://github.com/kaitranntt/ccs/issues/622';
|
||||
export const ANTIGRAVITY_PROJECT_ID_ISSUE_URL = 'https://github.com/kaitranntt/ccs/issues/619';
|
||||
@@ -47,6 +48,19 @@ function isTruthyEnv(value: string | undefined): boolean {
|
||||
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
||||
}
|
||||
|
||||
export function isAntigravityResponsibilityBypassEnabled(): boolean {
|
||||
if (isTruthyEnv(process.env.CCS_ACCEPT_AGY_RISK)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
const safety = getCliproxySafetyConfig();
|
||||
return safety.antigravity_ack_bypass === true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function askQuestion(rl: Interface, prompt: string): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
@@ -160,7 +174,7 @@ export function validateAntigravityRiskAcknowledgement(payload: unknown): Valida
|
||||
export async function ensureCliAntigravityResponsibility(
|
||||
options: EnsureCliRiskOptions
|
||||
): Promise<boolean> {
|
||||
if (options.acceptedByFlag || isTruthyEnv(process.env.CCS_ACCEPT_AGY_RISK)) {
|
||||
if (options.acceptedByFlag || isAntigravityResponsibilityBypassEnabled()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,12 @@ import {
|
||||
DEFAULT_CURSOR_CONFIG,
|
||||
DEFAULT_GLOBAL_ENV,
|
||||
DEFAULT_CLIPROXY_SERVER_CONFIG,
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG,
|
||||
DEFAULT_QUOTA_MANAGEMENT_CONFIG,
|
||||
DEFAULT_THINKING_CONFIG,
|
||||
DEFAULT_DASHBOARD_AUTH_CONFIG,
|
||||
DEFAULT_IMAGE_ANALYSIS_CONFIG,
|
||||
CLIProxySafetyConfig,
|
||||
GlobalEnvConfig,
|
||||
ThinkingConfig,
|
||||
DashboardAuthConfig,
|
||||
@@ -249,6 +251,11 @@ function mergeWithDefaults(partial: Partial<UnifiedConfig>): UnifiedConfig {
|
||||
request_log:
|
||||
partial.cliproxy?.logging?.request_log ?? defaults.cliproxy.logging?.request_log ?? false,
|
||||
},
|
||||
safety: {
|
||||
antigravity_ack_bypass:
|
||||
partial.cliproxy?.safety?.antigravity_ack_bypass ??
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG.antigravity_ack_bypass,
|
||||
},
|
||||
// Auth config - preserve user values, no defaults (uses constants as fallback)
|
||||
auth: partial.cliproxy?.auth,
|
||||
// Backend selection - validate and preserve user choice (original vs plus)
|
||||
@@ -892,6 +899,19 @@ export function getGlobalEnvConfig(): GlobalEnvConfig {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cliproxy safety configuration.
|
||||
* Returns defaults if not configured.
|
||||
*/
|
||||
export function getCliproxySafetyConfig(): CLIProxySafetyConfig {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
return {
|
||||
antigravity_ack_bypass:
|
||||
config.cliproxy?.safety?.antigravity_ack_bypass ??
|
||||
DEFAULT_CLIPROXY_SAFETY_CONFIG.antigravity_ack_bypass,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get thinking configuration.
|
||||
* Returns defaults if not configured.
|
||||
|
||||
@@ -156,6 +156,22 @@ export interface CLIProxyLoggingConfig {
|
||||
request_log?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* CLIProxy safety configuration.
|
||||
* Controls high-risk flow safeguards for supported providers.
|
||||
*/
|
||||
export interface CLIProxySafetyConfig {
|
||||
/** Allow skipping AGY responsibility acknowledgement flow (default: false) */
|
||||
antigravity_ack_bypass?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default CLIProxy safety configuration.
|
||||
*/
|
||||
export const DEFAULT_CLIPROXY_SAFETY_CONFIG: CLIProxySafetyConfig = {
|
||||
antigravity_ack_bypass: false,
|
||||
};
|
||||
|
||||
/**
|
||||
* Token refresh configuration.
|
||||
* Manages background token refresh worker settings.
|
||||
@@ -187,6 +203,8 @@ export interface CLIProxyConfig {
|
||||
variants: Record<string, CLIProxyVariantConfig | CompositeVariantConfig>;
|
||||
/** Logging configuration (disabled by default) */
|
||||
logging?: CLIProxyLoggingConfig;
|
||||
/** Safety controls for high-risk provider flows */
|
||||
safety?: CLIProxySafetyConfig;
|
||||
/** Kiro: disable incognito browser mode (use normal browser to save credentials) */
|
||||
kiro_no_incognito?: boolean;
|
||||
/** Global auth configuration for CLIProxyAPI */
|
||||
@@ -777,6 +795,7 @@ export function createEmptyUnifiedConfig(): UnifiedConfig {
|
||||
enabled: false,
|
||||
request_log: false,
|
||||
},
|
||||
safety: { ...DEFAULT_CLIPROXY_SAFETY_CONFIG },
|
||||
auto_sync: true,
|
||||
},
|
||||
preferences: {
|
||||
|
||||
@@ -46,7 +46,10 @@ import {
|
||||
import { getOAuthFlowType } from '../../cliproxy/provider-capabilities';
|
||||
import type { CLIProxyProvider } from '../../cliproxy/types';
|
||||
import { CLIPROXY_PROFILES } from '../../auth/profile-detector';
|
||||
import { validateAntigravityRiskAcknowledgement } from '../../cliproxy/antigravity-responsibility';
|
||||
import {
|
||||
validateAntigravityRiskAcknowledgement,
|
||||
isAntigravityResponsibilityBypassEnabled,
|
||||
} from '../../cliproxy/antigravity-responsibility';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -406,7 +409,7 @@ router.post('/:provider/start', async (req: Request, res: Response): Promise<voi
|
||||
return;
|
||||
}
|
||||
|
||||
if (provider === 'agy') {
|
||||
if (provider === 'agy' && !isAntigravityResponsibilityBypassEnabled()) {
|
||||
const validation = validateAntigravityRiskAcknowledgement(riskAcknowledgement);
|
||||
if (!validation.valid) {
|
||||
res.status(400).json({
|
||||
@@ -634,7 +637,7 @@ router.post('/:provider/start-url', async (req: Request, res: Response): Promise
|
||||
return;
|
||||
}
|
||||
|
||||
if (provider === 'agy') {
|
||||
if (provider === 'agy' && !isAntigravityResponsibilityBypassEnabled()) {
|
||||
const validation = validateAntigravityRiskAcknowledgement(riskAcknowledgement);
|
||||
if (!validation.valid) {
|
||||
res.status(400).json({
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '../../cliproxy';
|
||||
import { regenerateConfig } from '../../cliproxy/config-generator';
|
||||
import { deduplicateCcsHooks } from '../../utils/websearch/hook-utils';
|
||||
import { loadOrCreateUnifiedConfig, saveUnifiedConfig } from '../../config/unified-config-loader';
|
||||
import type { Settings } from '../../types/config';
|
||||
|
||||
const router = Router();
|
||||
@@ -383,6 +384,48 @@ router.delete('/:profile/presets/:name', (req: Request, res: Response): void =>
|
||||
|
||||
// ==================== Auth Tokens ====================
|
||||
|
||||
/**
|
||||
* GET /api/settings/auth/antigravity-risk - Get AGY responsibility bypass setting
|
||||
*/
|
||||
router.get('/auth/antigravity-risk', (_req: Request, res: Response): void => {
|
||||
try {
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
res.json({
|
||||
antigravityAckBypass: config.cliproxy?.safety?.antigravity_ack_bypass === true,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /api/settings/auth/antigravity-risk - Update AGY responsibility bypass setting
|
||||
*/
|
||||
router.put('/auth/antigravity-risk', (req: Request, res: Response): void => {
|
||||
try {
|
||||
const { antigravityAckBypass } = req.body as { antigravityAckBypass?: unknown };
|
||||
|
||||
if (typeof antigravityAckBypass !== 'boolean') {
|
||||
res.status(400).json({ error: 'antigravityAckBypass must be a boolean' });
|
||||
return;
|
||||
}
|
||||
|
||||
const config = loadOrCreateUnifiedConfig();
|
||||
config.cliproxy.safety = {
|
||||
...(config.cliproxy.safety ?? {}),
|
||||
antigravity_ack_bypass: antigravityAckBypass,
|
||||
};
|
||||
saveUnifiedConfig(config);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
antigravityAckBypass,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /api/settings/auth/tokens - Get current auth token status (masked)
|
||||
*/
|
||||
|
||||
@@ -1,12 +1,44 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
ANTIGRAVITY_ACK_PHRASE,
|
||||
ANTIGRAVITY_ACK_VERSION,
|
||||
hasAntigravityRiskAcceptanceFlag,
|
||||
isAntigravityResponsibilityBypassEnabled,
|
||||
validateAntigravityRiskAcknowledgement,
|
||||
} from '../../../src/cliproxy/antigravity-responsibility';
|
||||
|
||||
describe('antigravity-responsibility', () => {
|
||||
let tempHome = '';
|
||||
let originalCcsHome: string | undefined;
|
||||
let originalAgyRiskEnv: string | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-agy-risk-test-'));
|
||||
originalCcsHome = process.env.CCS_HOME;
|
||||
originalAgyRiskEnv = process.env.CCS_ACCEPT_AGY_RISK;
|
||||
process.env.CCS_HOME = tempHome;
|
||||
delete process.env.CCS_ACCEPT_AGY_RISK;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (originalCcsHome !== undefined) {
|
||||
process.env.CCS_HOME = originalCcsHome;
|
||||
} else {
|
||||
delete process.env.CCS_HOME;
|
||||
}
|
||||
|
||||
if (originalAgyRiskEnv !== undefined) {
|
||||
process.env.CCS_ACCEPT_AGY_RISK = originalAgyRiskEnv;
|
||||
} else {
|
||||
delete process.env.CCS_ACCEPT_AGY_RISK;
|
||||
}
|
||||
|
||||
fs.rmSync(tempHome, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('accepts a complete acknowledgement payload', () => {
|
||||
const result = validateAntigravityRiskAcknowledgement({
|
||||
version: ANTIGRAVITY_ACK_VERSION,
|
||||
@@ -75,4 +107,24 @@ describe('antigravity-responsibility', () => {
|
||||
expect(hasAntigravityRiskAcceptanceFlag(['--accept-antigravity-risk'])).toBeTrue();
|
||||
expect(hasAntigravityRiskAcceptanceFlag(['--auth'])).toBeFalse();
|
||||
});
|
||||
|
||||
it('enables bypass when CCS_ACCEPT_AGY_RISK is set', () => {
|
||||
process.env.CCS_ACCEPT_AGY_RISK = 'true';
|
||||
expect(isAntigravityResponsibilityBypassEnabled()).toBeTrue();
|
||||
});
|
||||
|
||||
it('enables bypass when cliproxy safety setting is enabled', () => {
|
||||
const ccsDir = path.join(tempHome, '.ccs');
|
||||
fs.mkdirSync(ccsDir, { recursive: true });
|
||||
fs.writeFileSync(
|
||||
path.join(ccsDir, 'config.yaml'),
|
||||
`version: 8
|
||||
cliproxy:
|
||||
safety:
|
||||
antigravity_ack_bypass: true
|
||||
`
|
||||
);
|
||||
|
||||
expect(isAntigravityResponsibilityBypassEnabled()).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Loader2, ExternalLink, User, Download, Copy, Check } from 'lucide-react';
|
||||
import { Loader2, ExternalLink, User, Download, Copy, Check, ShieldAlert } from 'lucide-react';
|
||||
import { useKiroImport } from '@/hooks/use-cliproxy';
|
||||
import { useCliproxyAuthFlow } from '@/hooks/use-cliproxy-auth-flow';
|
||||
import { applyDefaultPreset } from '@/lib/preset-utils';
|
||||
@@ -68,6 +68,8 @@ export function AddAccountDialog({
|
||||
const [localError, setLocalError] = useState<string | null>(null);
|
||||
const [acknowledgedRisk, setAcknowledgedRisk] = useState(false);
|
||||
const [agyRiskChecklist, setAgyRiskChecklist] = useState(DEFAULT_ANTIGRAVITY_RISK_CHECKLIST);
|
||||
const [agyAckBypassEnabled, setAgyAckBypassEnabled] = useState(false);
|
||||
const [agyAckBypassLoading, setAgyAckBypassLoading] = useState(false);
|
||||
const [kiroAuthMethod, setKiroAuthMethod] = useState<KiroAuthMethod>(DEFAULT_KIRO_AUTH_METHOD);
|
||||
const wasAuthenticatingRef = useRef(false);
|
||||
const authFlow = useCliproxyAuthFlow();
|
||||
@@ -75,7 +77,8 @@ export function AddAccountDialog({
|
||||
|
||||
const isKiro = provider === 'kiro';
|
||||
const requiresSafetyAcknowledgement = provider === 'gemini';
|
||||
const requiresAgyResponsibilityFlow = provider === 'agy';
|
||||
const requiresAgyResponsibilityFlow = provider === 'agy' && !agyAckBypassEnabled;
|
||||
const isAgyBypassStatePending = provider === 'agy' && agyAckBypassLoading;
|
||||
const isAgyRiskChecklistComplete = isAntigravityRiskChecklistComplete(agyRiskChecklist);
|
||||
const defaultDeviceCode = isDeviceCodeProvider(provider);
|
||||
const requiresNickname = isNicknameRequiredProvider(provider);
|
||||
@@ -92,6 +95,8 @@ export function AddAccountDialog({
|
||||
setLocalError(null);
|
||||
setAcknowledgedRisk(false);
|
||||
setAgyRiskChecklist(DEFAULT_ANTIGRAVITY_RISK_CHECKLIST);
|
||||
setAgyAckBypassEnabled(false);
|
||||
setAgyAckBypassLoading(false);
|
||||
setKiroAuthMethod(DEFAULT_KIRO_AUTH_METHOD);
|
||||
wasAuthenticatingRef.current = false;
|
||||
onClose();
|
||||
@@ -105,6 +110,44 @@ export function AddAccountDialog({
|
||||
}
|
||||
}, [provider, open]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
if (!open || provider !== 'agy') {
|
||||
setAgyAckBypassEnabled(false);
|
||||
setAgyAckBypassLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const loadAgyBypassState = async () => {
|
||||
try {
|
||||
setAgyAckBypassLoading(true);
|
||||
const response = await fetch('/api/settings/auth/antigravity-risk');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load Antigravity power user setting');
|
||||
}
|
||||
const data = (await response.json()) as { antigravityAckBypass?: boolean };
|
||||
if (!cancelled) {
|
||||
setAgyAckBypassEnabled(data.antigravityAckBypass === true);
|
||||
}
|
||||
} catch {
|
||||
if (!cancelled) {
|
||||
setAgyAckBypassEnabled(false);
|
||||
}
|
||||
} finally {
|
||||
if (!cancelled) {
|
||||
setAgyAckBypassLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadAgyBypassState();
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, provider]);
|
||||
|
||||
// When authFlow completes successfully (polling detected success), apply preset and close
|
||||
useEffect(() => {
|
||||
if (!authFlow.isAuthenticating && !authFlow.error && authFlow.provider === null && open) {
|
||||
@@ -153,6 +196,10 @@ export function AddAccountDialog({
|
||||
* - Authorization code providers use /start-url and polling.
|
||||
*/
|
||||
const handleAuthenticate = () => {
|
||||
if (isAgyBypassStatePending) {
|
||||
setLocalError('Loading Antigravity safety settings. Please wait a moment and retry.');
|
||||
return;
|
||||
}
|
||||
if (requiresAgyResponsibilityFlow && !isAgyRiskChecklistComplete) {
|
||||
setLocalError(
|
||||
'Complete all Antigravity responsibility steps before authenticating this provider.'
|
||||
@@ -241,6 +288,17 @@ export function AddAccountDialog({
|
||||
/>
|
||||
)}
|
||||
|
||||
{provider === 'agy' && agyAckBypassEnabled && !showAuthUI && (
|
||||
<div className="rounded-lg border border-amber-400/35 bg-amber-50/70 p-3 text-xs text-amber-900 dark:border-amber-800/60 dark:bg-amber-950/25 dark:text-amber-100">
|
||||
<div className="mb-1.5 flex items-center gap-1.5 font-semibold">
|
||||
<ShieldAlert className="h-3.5 w-3.5" />
|
||||
Power user mode enabled
|
||||
</div>
|
||||
AGY responsibility checklist is skipped from Settings {'>'} Auth. You accept full
|
||||
responsibility for OAuth/account risk.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{requiresSafetyAcknowledgement && !showAuthUI && (
|
||||
<AccountSafetyWarningCard
|
||||
provider="gemini"
|
||||
@@ -443,6 +501,7 @@ export function AddAccountDialog({
|
||||
onClick={handleAuthenticate}
|
||||
disabled={
|
||||
isPending ||
|
||||
isAgyBypassStatePending ||
|
||||
(requiresNickname && !nicknameTrimmed) ||
|
||||
(requiresAgyResponsibilityFlow && !isAgyRiskChecklistComplete) ||
|
||||
(requiresSafetyAcknowledgement && !acknowledgedRisk)
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
RefreshCw,
|
||||
CheckCircle2,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
Check,
|
||||
KeyRound,
|
||||
ShieldCheck,
|
||||
ShieldAlert,
|
||||
Save,
|
||||
} from 'lucide-react';
|
||||
import { useRawConfig } from '../hooks';
|
||||
@@ -51,6 +53,9 @@ export default function AuthSection() {
|
||||
const [editedSecret, setEditedSecret] = useState<string | null>(null);
|
||||
const [copiedApiKey, setCopiedApiKey] = useState(false);
|
||||
const [copiedSecret, setCopiedSecret] = useState(false);
|
||||
const [agyAckBypass, setAgyAckBypass] = useState(false);
|
||||
const [agyAckBypassLoading, setAgyAckBypassLoading] = useState(true);
|
||||
const [agyAckBypassSaving, setAgyAckBypassSaving] = useState(false);
|
||||
|
||||
// Fetch tokens
|
||||
const fetchTokens = useCallback(async () => {
|
||||
@@ -71,11 +76,28 @@ export default function AuthSection() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const fetchAgyAckBypass = useCallback(async () => {
|
||||
try {
|
||||
setAgyAckBypassLoading(true);
|
||||
const response = await fetch('/api/settings/auth/antigravity-risk');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load Antigravity power user settings');
|
||||
}
|
||||
const data = (await response.json()) as { antigravityAckBypass?: boolean };
|
||||
setAgyAckBypass(data.antigravityAckBypass === true);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setAgyAckBypassLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Load on mount
|
||||
useEffect(() => {
|
||||
fetchTokens();
|
||||
fetchAgyAckBypass();
|
||||
fetchRawConfig();
|
||||
}, [fetchTokens, fetchRawConfig]);
|
||||
}, [fetchTokens, fetchAgyAckBypass, fetchRawConfig]);
|
||||
|
||||
// Clear success after timeout
|
||||
useEffect(() => {
|
||||
@@ -197,6 +219,41 @@ export default function AuthSection() {
|
||||
setTimeout(() => setCopiedSecret(false), 2000);
|
||||
};
|
||||
|
||||
const saveAgyAckBypass = async (nextValue: boolean) => {
|
||||
if (nextValue) {
|
||||
const confirmed = window.confirm(
|
||||
'Enable Antigravity power user mode?\n\nThis disables AGY responsibility checklist prompts in CLI and dashboard. You accept full responsibility for OAuth/account risk.'
|
||||
);
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
try {
|
||||
setAgyAckBypassSaving(true);
|
||||
setError(null);
|
||||
|
||||
const response = await fetch('/api/settings/auth/antigravity-risk', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ antigravityAckBypass: nextValue }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const data = (await response.json()) as { error?: string };
|
||||
throw new Error(data.error || 'Failed to update Antigravity power user mode');
|
||||
}
|
||||
|
||||
setAgyAckBypass(nextValue);
|
||||
setSuccess(
|
||||
nextValue ? 'Antigravity power user mode enabled.' : 'Antigravity power user mode disabled.'
|
||||
);
|
||||
await fetchRawConfig();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||||
} finally {
|
||||
setAgyAckBypassSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading || !tokens) {
|
||||
return (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
@@ -360,6 +417,29 @@ export default function AuthSection() {
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="space-y-3 rounded-lg border border-amber-400/35 bg-amber-50/70 p-4 dark:border-amber-800/60 dark:bg-amber-950/25">
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldAlert className="w-4 h-4 text-amber-700 dark:text-amber-300" />
|
||||
<h3 className="text-base font-medium">Antigravity Power User Mode</h3>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Skip AGY responsibility checklists in Add Account and `ccs agy` flows.
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={agyAckBypass}
|
||||
disabled={agyAckBypassLoading || agyAckBypassSaving || saving}
|
||||
onCheckedChange={saveAgyAckBypass}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-xs text-amber-800/90 dark:text-amber-200/90">
|
||||
Use only if you fully understand the OAuth suspension/ban risk pattern (#622). CCS
|
||||
cannot assume responsibility for account loss.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="pt-4 border-t">
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -385,6 +465,7 @@ export default function AuthSection() {
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
fetchTokens();
|
||||
fetchAgyAckBypass();
|
||||
fetchRawConfig();
|
||||
}}
|
||||
disabled={loading || saving}
|
||||
|
||||
Reference in New Issue
Block a user