feat: implement menu service for context menu management

feat: add provider API service for managing provider accounts and keys
feat: create provider runtime sync service for agent runtime management
feat: introduce script execution service for running automation scripts
feat: develop script store service for managing script metadata and storage
feat: implement theme service for managing application theme settings
feat: add updater service for handling application updates
feat: create window service for managing application windows and their states
This commit is contained in:
DEV_DSW
2026-04-22 09:26:39 +08:00
parent 9b8214cdd4
commit 416399e7a8
19 changed files with 33 additions and 33 deletions

View File

@@ -0,0 +1,231 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import logManager from '@electron/service/logger';
import { providerApiService } from '@electron/service/provider-api-service';
import { listAgentsSnapshot } from '@electron/utils/agent-config';
import { ensureDir, ensureOpenClawRuntimeLayout, getOpenClawRuntimePaths } from '@electron/utils/paths';
import type { AgentSummary, AgentsSnapshot } from '@runtime/lib/agents';
import type { ProviderAccount } from '@runtime/lib/providers';
interface AgentRuntimeSyncMeta {
agentId: string;
providerAccountId: string | null;
modelRef: string | null;
inheritedModel: boolean;
runtimeDir: string;
}
export interface ProviderRuntimeSyncResult {
syncedAt: string;
agentCount: number;
defaultAccountId: string | null;
globalRegistryPath: string;
agents: AgentRuntimeSyncMeta[];
warnings: string[];
}
interface RuntimeProviderProfile {
id: string;
vendorId: string;
label: string;
authMode: string;
apiKey: string | null;
hasKey: boolean;
baseUrl?: string;
apiProtocol?: string;
headers?: Record<string, string>;
metadata?: ProviderAccount['metadata'];
}
const AGENT_RUNTIME_DIR_NAME = 'runtime';
const AUTH_PROFILES_FILE_NAME = 'auth-profiles.json';
const MODELS_FILE_NAME = 'models.json';
const OPENCLAW_FILE_NAME = 'openclaw.json';
const GLOBAL_REGISTRY_FILE_NAME = 'agents-runtime.json';
function writeJson(filePath: string, data: unknown): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
}
function getEnabledAccounts(accounts?: ProviderAccount[]): ProviderAccount[] {
const source = Array.isArray(accounts) ? accounts : providerApiService.getAccounts();
return source.filter((account) => account.enabled !== false);
}
function resolveEffectiveAccount(
agent: AgentSummary,
accounts: ProviderAccount[],
defaultAccountId: string | null,
): ProviderAccount | null {
if (agent.providerAccountId) {
return accounts.find((account) => account.id === agent.providerAccountId) ?? null;
}
if (defaultAccountId) {
return accounts.find((account) => account.id === defaultAccountId) ?? null;
}
return accounts[0] ?? null;
}
function buildRuntimeProfile(account: ProviderAccount | null): RuntimeProviderProfile[] {
if (!account) return [];
const apiKey = providerApiService.getApiKey(account.id).apiKey;
return [{
id: account.id,
vendorId: account.vendorId,
label: account.label,
authMode: account.authMode,
apiKey: apiKey || null,
hasKey: Boolean(apiKey),
baseUrl: account.baseUrl,
apiProtocol: account.apiProtocol,
headers: account.headers,
metadata: account.metadata,
}];
}
function buildRuntimeModelEntry(
agent: AgentSummary,
effectiveAccount: ProviderAccount | null,
defaultModelRef: string | null,
) {
const effectiveModelRef = agent.modelRef ?? effectiveAccount?.model ?? defaultModelRef ?? null;
if (!effectiveModelRef) {
return [];
}
return [{
id: effectiveModelRef,
modelRef: effectiveModelRef,
providerAccountId: effectiveAccount?.id ?? null,
vendorId: effectiveAccount?.vendorId ?? agent.vendorId ?? null,
baseUrl: effectiveAccount?.baseUrl ?? null,
apiProtocol: effectiveAccount?.apiProtocol ?? null,
headers: effectiveAccount?.headers ?? {},
fallbackModels: effectiveAccount?.fallbackModels ?? [],
fallbackAccountIds: effectiveAccount?.fallbackAccountIds ?? [],
inheritedModel: Boolean(agent.inheritedModel),
source: agent.overrideModelRef ? 'agent-override' : 'provider-default',
}];
}
function ensureAgentRuntimeDir(agent: AgentSummary): string {
const agentDir = agent.agentDir?.trim();
if (!agentDir) {
throw new Error(`Agent "${agent.id}" is missing agentDir`);
}
return ensureDir(path.join(agentDir, AGENT_RUNTIME_DIR_NAME));
}
function writeAgentRuntimeFiles(
agent: AgentSummary,
snapshot: AgentsSnapshot,
accounts: ProviderAccount[],
defaultAccountId: string | null,
syncedAt: string,
warnings: string[],
): AgentRuntimeSyncMeta {
const runtimeDir = ensureAgentRuntimeDir(agent);
const effectiveAccount = resolveEffectiveAccount(agent, accounts, defaultAccountId);
const profiles = buildRuntimeProfile(effectiveAccount);
const models = buildRuntimeModelEntry(agent, effectiveAccount, snapshot.defaultModelRef);
if (agent.providerAccountId && !effectiveAccount) {
warnings.push(`Agent "${agent.id}" references missing provider account "${agent.providerAccountId}"`);
}
if (!effectiveAccount) {
warnings.push(`Agent "${agent.id}" has no enabled provider account available`);
}
if (models.length === 0) {
warnings.push(`Agent "${agent.id}" has no model configured after runtime sync`);
}
writeJson(path.join(runtimeDir, AUTH_PROFILES_FILE_NAME), {
version: 1,
syncedAt,
agentId: agent.id,
defaultProfileId: effectiveAccount?.id ?? null,
profiles,
});
writeJson(path.join(runtimeDir, MODELS_FILE_NAME), {
version: 1,
syncedAt,
agentId: agent.id,
defaultModelRef: models[0]?.modelRef ?? null,
models,
});
writeJson(path.join(runtimeDir, OPENCLAW_FILE_NAME), {
version: 1,
syncedAt,
agentId: agent.id,
agentName: agent.name,
mainSessionKey: agent.mainSessionKey,
workspace: agent.workspace ?? null,
agentDir: agent.agentDir ?? null,
providerAccountId: effectiveAccount?.id ?? null,
defaultProviderAccountId: snapshot.defaultProviderAccountId,
modelRef: models[0]?.modelRef ?? null,
defaultModelRef: snapshot.defaultModelRef,
inheritedModel: Boolean(agent.inheritedModel),
});
return {
agentId: agent.id,
providerAccountId: effectiveAccount?.id ?? null,
modelRef: models[0]?.modelRef ?? null,
inheritedModel: Boolean(agent.inheritedModel),
runtimeDir,
};
}
export function syncProviderRuntimeSnapshot(options?: {
accounts?: ProviderAccount[];
defaultAccountId?: string | null;
snapshot?: AgentsSnapshot;
}): ProviderRuntimeSyncResult {
const syncedAt = new Date().toISOString();
const accounts = getEnabledAccounts(options?.accounts);
const defaultAccountId = options?.defaultAccountId ?? providerApiService.getDefault().accountId;
const snapshot = options?.snapshot ?? listAgentsSnapshot(accounts, defaultAccountId);
const warnings: string[] = [];
const runtimePaths = ensureOpenClawRuntimeLayout(getOpenClawRuntimePaths());
const agents = snapshot.agents.map((agent) =>
writeAgentRuntimeFiles(agent, snapshot, accounts, defaultAccountId, syncedAt, warnings),
);
const globalRegistryPath = path.join(runtimePaths.runtimeDir, GLOBAL_REGISTRY_FILE_NAME);
writeJson(globalRegistryPath, {
version: 1,
syncedAt,
defaultAccountId,
defaultModelRef: snapshot.defaultModelRef,
agents,
});
if (warnings.length > 0) {
logManager.warn('Provider runtime sync completed with warnings', warnings);
} else {
logManager.info('Provider runtime sync completed', {
agentCount: agents.length,
globalRegistryPath,
});
}
return {
syncedAt,
agentCount: agents.length,
defaultAccountId,
globalRegistryPath,
agents,
warnings,
};
}