feat: refactor HomePage to integrate agents store and update related components
feat: add runtime event handling for providers in ProvidersSection feat: update routing to include Channels and Agents pages feat: extend route types and navigation items for Channels and Agents feat: implement agents store for managing agent data and interactions fix: update chat store to utilize agents store for agent-related functionality chore: export agents store from index fix: enhance runtime types for better event handling fix: update Vite config to handle dev server URL correctly
This commit is contained in:
@@ -4,6 +4,7 @@ import { CONFIG_KEYS, IPC_EVENTS } from '@runtime/lib/constants'
|
||||
import { debounce } from '@runtime/lib/utils'
|
||||
|
||||
import logManager from '@electron/service/logger'
|
||||
import { getUserDataDir } from '@electron/utils/paths'
|
||||
|
||||
const DEFAULT_CONFIG: IConfig = {
|
||||
[CONFIG_KEYS.THEME_MODE]: 'system',
|
||||
@@ -35,6 +36,7 @@ export class ConfigService {
|
||||
const { default: Store } = await import('electron-store');
|
||||
this._store = new Store<IConfig>({
|
||||
name: 'config',
|
||||
cwd: getUserDataDir(),
|
||||
defaults: DEFAULT_CONFIG,
|
||||
});
|
||||
this._setupIpcEvents();
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { IPC_EVENTS } from '@runtime/lib/constants';
|
||||
import { promisify } from 'util';
|
||||
import { app, ipcMain } from 'electron';
|
||||
import { ipcMain } from 'electron';
|
||||
import log from 'electron-log';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import { getUserDataDir } from '@electron/utils/paths';
|
||||
|
||||
// 转换为Promise形式的fs方法
|
||||
const readdirAsync = promisify(fs.readdir);
|
||||
@@ -20,7 +21,7 @@ class LogService {
|
||||
private readonly CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
private constructor() {
|
||||
const logPath = path.join(app.getPath('userData'), 'logs');
|
||||
const logPath = path.join(getUserDataDir(), 'logs');
|
||||
// c:users/{username}/AppData/Roaming/{appName}/logs
|
||||
|
||||
// 创建日志目录
|
||||
@@ -81,7 +82,7 @@ class LogService {
|
||||
|
||||
private async _cleanupOldLogs() {
|
||||
try {
|
||||
const logPath = path.join(app.getPath('userData'), 'logs');
|
||||
const logPath = path.join(getUserDataDir(), 'logs');
|
||||
|
||||
if (!fs.existsSync(logPath)) return;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import logManager from '@electron/service/logger';
|
||||
@@ -8,6 +7,7 @@ import type {
|
||||
ProviderVendorInfo,
|
||||
ProviderWithKeyInfo,
|
||||
} from '@runtime/lib/providers';
|
||||
import { getUserDataDir } from '@electron/utils/paths';
|
||||
|
||||
interface ProviderStore {
|
||||
accounts: ProviderAccount[];
|
||||
@@ -19,8 +19,8 @@ const defaultStore: ProviderStore = {
|
||||
defaultAccountId: null,
|
||||
};
|
||||
|
||||
const storePath = path.join(app.getPath('userData'), 'provider-accounts.json');
|
||||
const keysPath = path.join(app.getPath('userData'), 'provider-keys.json');
|
||||
const storePath = path.join(getUserDataDir(), 'provider-accounts.json');
|
||||
const keysPath = path.join(getUserDataDir(), 'provider-keys.json');
|
||||
|
||||
function readJson<T>(filePath: string, defaultValue: T): T {
|
||||
try {
|
||||
|
||||
231
electron/service/provider-runtime-sync/index.ts
Normal file
231
electron/service/provider-runtime-sync/index.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -9,6 +9,9 @@ type TabId = string
|
||||
type TabInfo = { id: TabId; url: string; title: string; isLoading: boolean; canGoBack: boolean; canGoForward: boolean }
|
||||
|
||||
const UI_HEIGHT = 88
|
||||
const preloadEntryPath = MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||
? path.join(process.cwd(), 'dist-electron', 'preload', 'preload.js')
|
||||
: path.join(__dirname, '..', 'preload', 'preload.js')
|
||||
|
||||
export class TabManager {
|
||||
private win: BrowserWindow
|
||||
@@ -99,9 +102,7 @@ export class TabManager {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: true,
|
||||
preload: MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||
? path.join(process.cwd(), 'dist-electron/preload/preload.js')
|
||||
: path.join(__dirname, 'preload.js'),
|
||||
preload: preloadEntryPath,
|
||||
},
|
||||
})
|
||||
this.views.set(id, view)
|
||||
|
||||
@@ -30,6 +30,9 @@ interface SizeOptions {
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
const useCustomTitleBar = isWindows;
|
||||
const preloadEntryPath = MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||
? path.join(process.cwd(), 'dist-electron', 'preload', 'preload.js')
|
||||
: path.join(__dirname, '..', 'preload', 'preload.js');
|
||||
|
||||
function getSharedWindowOptions(): BrowserWindowConstructorOptions {
|
||||
return {
|
||||
@@ -45,9 +48,7 @@ function getSharedWindowOptions(): BrowserWindowConstructorOptions {
|
||||
contextIsolation: true, // 启用上下文隔离,防止渲染进程访问主进程 API
|
||||
sandbox: true, // 启用沙箱模式,进一步增强安全性
|
||||
backgroundThrottling: false,
|
||||
preload: MAIN_WINDOW_VITE_DEV_SERVER_URL
|
||||
? path.join(process.cwd(), 'dist-electron/preload/preload.js')
|
||||
: path.join(__dirname, 'preload.js'),
|
||||
preload: preloadEntryPath,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user