feat(grokbuild): add Grok Official provider with official-state import

Add a "Grok Official" preset and seed (grokbuild-official) whose empty
config represents the official login state: no custom [model.*] tables
are written, so Grok CLI falls back to its built-in xAI OAuth login and
cc-switch never touches those credentials.

Backend:
- Seed entry in OFFICIAL_SEEDS plus ensure_grokbuild_official_provider
  command for on-demand repair (the one-shot master seeding flag is
  already set for existing databases).
- Split validation into syntax-only (empty allowed) for live reads,
  writes and official snapshots, keeping the full custom-model shape
  check for non-official provider writes and imports. Backup/restore
  can now round-trip an official-state live file.
- Manual import (command layer only) recognizes an official-state live
  config and imports it as the official entry set as current, matching
  the Codex official-login import outcome. Startup auto-import keeps
  rejecting official-state live so a deleted official entry is never
  resurrected on launch: startup import only captures real user data
  as "default" and never manufactures official entries.
- Manual import also ensures the official entry before importing
  (claude-desktop precedent) and after a successful custom import, so
  first-time users end up with default + official like other apps.
- Proxy takeover guards skip or reject official-state live configs in
  all three takeover paths, consistent with the official-provider
  takeover ban.

Frontend:
- Grok Official preset entry in the GrokBuild form: official category
  hides connection fields and passes the raw config through untouched.
- Filter managed-OAuth presets out of the GrokBuild preset list; they
  were never wired for this app and produced keyless broken configs.

Tests cover seed presence, official round-trip, ensure-after-deletion,
and the four import scenarios including startup non-resurrection.
This commit is contained in:
Jason
2026-07-21 15:31:27 +08:00
parent a5aa1fd82b
commit f733def452
16 changed files with 700 additions and 144 deletions
+4
View File
@@ -107,6 +107,10 @@ export const providersApi = {
return await invoke("ensure_codex_official_provider");
},
async ensureGrokBuildOfficialProvider(): Promise<boolean> {
return await invoke("ensure_grokbuild_official_provider");
},
async getClaudeDesktopStatus(): Promise<ClaudeDesktopStatus> {
return await invoke("get_claude_desktop_status");
},
+16 -1
View File
@@ -10,7 +10,10 @@ import { generateUUID } from "@/utils/uuid";
import { openclawKeys } from "@/hooks/useOpenClaw";
import { invalidateHermesProviderCaches } from "@/hooks/useHermes";
import { usageKeys } from "@/lib/query/usage";
import { CODEX_OFFICIAL_PROVIDER_ID } from "@/utils/providerCapabilities";
import {
CODEX_OFFICIAL_PROVIDER_ID,
GROKBUILD_OFFICIAL_PROVIDER_ID,
} from "@/utils/providerCapabilities";
export const useAddProviderMutation = (appId: AppId) => {
const queryClient = useQueryClient();
@@ -23,6 +26,7 @@ export const useAddProviderMutation = (appId: AppId) => {
addToLive?: boolean;
ensureClaudeDesktopOfficialSeed?: boolean;
ensureCodexOfficialSeed?: boolean;
ensureGrokBuildOfficialSeed?: boolean;
},
) => {
const {
@@ -30,6 +34,7 @@ export const useAddProviderMutation = (appId: AppId) => {
addToLive,
ensureClaudeDesktopOfficialSeed,
ensureCodexOfficialSeed,
ensureGrokBuildOfficialSeed,
...rest
} = providerInput;
@@ -53,6 +58,16 @@ export const useAddProviderMutation = (appId: AppId) => {
return officialProvider;
}
if (appId === "grokbuild" && ensureGrokBuildOfficialSeed) {
await providersApi.ensureGrokBuildOfficialProvider();
const providers = await providersApi.getAll(appId);
const officialProvider = providers[GROKBUILD_OFFICIAL_PROVIDER_ID];
if (!officialProvider) {
throw new Error("Grok Build official provider was not created");
}
return officialProvider;
}
let id: string;
if (appId === "opencode" || appId === "openclaw" || appId === "hermes") {