Refine desktop setup and remove bundled app center apps

This commit is contained in:
inman
2026-06-04 09:58:58 +08:00
parent 6153579b90
commit 84128dbe23
73 changed files with 3888 additions and 2024 deletions

View File

@@ -1,5 +1,5 @@
import type { GatewayManager } from '../../gateway/manager';
import { getProviderAccount, listProviderAccounts } from './provider-store';
import { getProviderAccount, listProviderAccounts, providerAccountToConfig } from './provider-store';
import { getProviderSecret } from '../secrets/secret-store';
import type { ProviderConfig } from '../../utils/secure-storage';
import { getAllProviders, getApiKey, getDefaultProvider, getProvider } from '../../utils/secure-storage';
@@ -144,6 +144,16 @@ export function getProviderModelRef(config: ProviderConfig): string | undefined
: `${providerKey}/${defaultModel}`;
}
async function getProviderConfigForRuntime(providerId: string): Promise<ProviderConfig | null> {
const legacyProvider = await getProvider(providerId);
if (legacyProvider) {
return legacyProvider;
}
const account = await getProviderAccount(providerId);
return account ? providerAccountToConfig(account) : null;
}
export async function getProviderFallbackModelRefs(config: ProviderConfig): Promise<string[]> {
const allProviders = await getAllProviders();
const providerMap = new Map(allProviders.map((provider) => [provider.id, provider]));
@@ -495,7 +505,8 @@ export async function syncSavedProviderToRuntime(
scheduleGatewayRefresh(
gatewayManager,
`Scheduling Gateway reload after saving provider "${context.runtimeProviderKey}" config`,
`Scheduling Gateway restart after saving provider "${context.runtimeProviderKey}" config`,
{ mode: 'restart' },
);
}
@@ -543,7 +554,8 @@ export async function syncUpdatedProviderToRuntime(
scheduleGatewayRefresh(
gatewayManager,
`Scheduling Gateway reload after updating provider "${ock}" config`,
`Scheduling Gateway restart after updating provider "${ock}" config`,
{ mode: 'restart' },
);
}
@@ -584,7 +596,7 @@ export async function syncDefaultProviderToRuntime(
providerId: string,
gatewayManager?: GatewayManager,
): Promise<void> {
const provider = await getProvider(providerId);
const provider = await getProviderConfigForRuntime(providerId);
if (!provider) {
return;
}
@@ -656,7 +668,8 @@ export async function syncDefaultProviderToRuntime(
}
scheduleGatewayRefresh(
gatewayManager,
`Scheduling Gateway reload after provider switch to "${browserOAuthRuntimeProvider}"`,
`Scheduling Gateway restart after provider switch to "${browserOAuthRuntimeProvider}"`,
{ mode: 'restart' },
);
return;
}
@@ -718,7 +731,7 @@ export async function syncDefaultProviderToRuntime(
scheduleGatewayRefresh(
gatewayManager,
`Scheduling Gateway reload after provider switch to "${ock}"`,
{ onlyIfRunning: true },
`Scheduling Gateway restart after provider switch to "${ock}"`,
{ onlyIfRunning: true, mode: 'restart' },
);
}

View File

@@ -32,6 +32,12 @@ import { getActiveOpenClawProviders, getOpenClawProvidersConfig } from '../../ut
import { getAliasSourceTypes, getOpenClawProviderKeyForType } from '../../utils/provider-keys';
import type { ProviderWithKeyInfo } from '../../shared/providers/types';
import { logger } from '../../utils/logger';
import {
YINIAN_MODEL_DEFAULT_BASE_URL,
YINIAN_MODEL_DEFAULT_ID,
YINIAN_MODEL_PROVIDER_KEY,
YINIAN_MODEL_REF,
} from '../../../shared/yinian-model';
function maskApiKey(apiKey: string | null): string | null {
if (!apiKey) return null;
@@ -67,6 +73,35 @@ function inferProviderVendorIdFromOpenClawEntry(
return ((BUILTIN_PROVIDER_TYPES as readonly string[]).includes(key) ? key : 'custom') as ProviderType | 'custom';
}
function isPlaceholderYinianModelAccount(account: ProviderAccount): boolean {
const model = account.model?.startsWith(`${YINIAN_MODEL_PROVIDER_KEY}/`)
? account.model
: `${YINIAN_MODEL_PROVIDER_KEY}/${account.model ?? ''}`;
return account.id === YINIAN_MODEL_PROVIDER_KEY
&& account.vendorId === 'custom'
&& account.baseUrl === YINIAN_MODEL_DEFAULT_BASE_URL
&& model === YINIAN_MODEL_REF;
}
function isPlaceholderYinianModelEntry(
key: string,
entry: Record<string, unknown>,
defaultModel: string | undefined,
): boolean {
if (key !== YINIAN_MODEL_PROVIDER_KEY) return false;
if (!entry.baseUrl) return true;
if (entry.baseUrl !== YINIAN_MODEL_DEFAULT_BASE_URL) return false;
const models = Array.isArray(entry.models) ? entry.models : [];
const hasPlaceholderModel = models.some((model) => {
if (!model || typeof model !== 'object') return false;
return (model as Record<string, unknown>).id === YINIAN_MODEL_DEFAULT_ID;
});
return defaultModel === YINIAN_MODEL_REF || hasPlaceholderModel;
}
export class ProviderService {
async listVendors(): Promise<ProviderDefinition[]> {
return PROVIDER_DEFINITIONS;
@@ -75,21 +110,16 @@ export class ProviderService {
async listAccounts(): Promise<ProviderAccount[]> {
await ensureProviderStoreMigrated();
// ── openclaw.json is the ONLY source of truth ──
// The provider list is derived entirely from openclaw.json.
// The electron-store is only used as a metadata cache (label, authMode, etc.).
// Provider accounts are the settings source of truth. openclaw.json is the
// generated runtime output, but we still import from it for older installs.
const { providers: openClawProviders, defaultModel } = await getOpenClawProvidersConfig();
const activeProviders = await getActiveOpenClawProviders();
if (activeProviders.size === 0) {
return [];
}
const rawStoreAccounts = await listProviderAccounts();
const allStoreAccounts = rawStoreAccounts
.filter((account) => !isPlaceholderYinianModelAccount(account));
// Read store accounts as a lookup cache (NOT as the source of what to display).
const allStoreAccounts = await listProviderAccounts();
// Index store accounts by their openclaw runtime key for fast lookup.
const storeByKey = new Map<string, ProviderAccount[]>();
for (const account of allStoreAccounts) {
const ock = getOpenClawProviderKeyForType(account.vendorId, account.id);
@@ -101,47 +131,59 @@ export class ProviderService {
const result: ProviderAccount[] = [];
const processedKeys = new Set<string>();
// For each active provider in openclaw.json, produce exactly ONE account.
for (const key of activeProviders) {
const storeKeys = Array.from(storeByKey.keys());
for (const key of storeKeys) {
if (processedKeys.has(key)) continue;
processedKeys.add(key);
const storeGroup = storeByKey.get(key) ?? [];
const aliasAccounts = storeGroup.filter((a) => a.vendorId !== key);
const candidates = aliasAccounts.length > 0 ? aliasAccounts : storeGroup;
candidates.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
result.push(candidates[0]);
if (storeGroup.length > 0) {
// Pick the best store account for this key:
// 1. Prefer alias variants (e.g. minimax-portal-cn over minimax-portal)
// 2. Among equal variants, prefer the most recently updated
const aliasAccounts = storeGroup.filter((a) => a.vendorId !== key);
const candidates = aliasAccounts.length > 0 ? aliasAccounts : storeGroup;
candidates.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
result.push(candidates[0]);
// Clean up orphaned duplicates from the store.
const kept = candidates[0];
for (const account of storeGroup) {
if (account.id !== kept.id) {
logger.info(
`[provider-sync] Removing orphaned account "${account.id}" for key "${key}" (keeping "${kept.id}")`,
);
await deleteProviderAccount(account.id);
}
}
} else {
// No store account for this key — create a seed from openclaw.json.
const entry = openClawProviders[key];
if (entry) {
const seeded = ProviderService.buildAccountsFromOpenClawEntries(
{ [key]: entry },
new Set(),
new Set(),
defaultModel,
const kept = candidates[0];
for (const account of storeGroup) {
if (account.id !== kept.id) {
logger.info(
`[provider-sync] Removing orphaned account "${account.id}" for key "${key}" (keeping "${kept.id}")`,
);
for (const account of seeded) {
await saveProviderAccount(account);
result.push(account);
logger.info(`[provider-sync] Seeded provider account "${account.id}" from openclaw.json`);
}
await deleteProviderAccount(account.id);
}
}
}
const existingIds = new Set(result.map((account) => account.id));
const existingVendorIds = new Set(result.map((account) => account.vendorId));
for (const key of activeProviders) {
if (processedKeys.has(key)) continue;
const entry = openClawProviders[key];
if (!entry || isPlaceholderYinianModelEntry(key, entry, defaultModel)) {
continue;
}
const seeded = ProviderService.buildAccountsFromOpenClawEntries(
{ [key]: entry },
existingIds,
existingVendorIds,
defaultModel,
);
for (const account of seeded) {
await saveProviderAccount(account);
result.push(account);
existingIds.add(account.id);
existingVendorIds.add(account.vendorId);
logger.info(`[provider-sync] Seeded provider account "${account.id}" from openclaw.json`);
}
}
for (const account of rawStoreAccounts) {
if (isPlaceholderYinianModelAccount(account)) {
try {
await deleteProviderAccount(account.id);
} catch (err) {
logger.warn(`[provider-sync] Failed to remove placeholder model account "${account.id}":`, err);
}
}
}