feat: update desktop workflows and app center

This commit is contained in:
inman
2026-05-13 19:14:56 +08:00
parent 20b5aff4ad
commit 7c8781a6e3
160 changed files with 55492 additions and 1423 deletions

View File

@@ -0,0 +1,41 @@
/**
* Application-level local preferences.
*
* Renderer localStorage is origin-scoped, so dev server port changes can make
* user preferences appear to disappear. Keep business-local preferences here
* as the stable source shared by all renderer origins.
*/
export interface AppLocalPreferences {
quickTasks?: unknown[];
channelAccountRemarks?: Record<string, string>;
desktopUserName?: string;
workspaceDisplayName?: string;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let localPreferencesStoreInstance: any = null;
async function getLocalPreferencesStore() {
if (!localPreferencesStoreInstance) {
const Store = (await import('electron-store')).default;
localPreferencesStoreInstance = new Store<AppLocalPreferences>({
name: 'local-preferences',
});
}
return localPreferencesStoreInstance;
}
export async function getAllLocalPreferences(): Promise<AppLocalPreferences> {
const store = await getLocalPreferencesStore();
return { ...(store.store ?? {}) };
}
export async function patchLocalPreferences(patch: AppLocalPreferences): Promise<AppLocalPreferences> {
const store = await getLocalPreferencesStore();
for (const [key, value] of Object.entries(patch) as Array<[keyof AppLocalPreferences, unknown]>) {
if (value === undefined) continue;
store.set(key, value);
}
return { ...(store.store ?? {}) };
}

View File

@@ -12,6 +12,11 @@ import {
import { logger } from './logger';
import { getOpenClawConfigDir, needsWinShell, quoteForCmd } from './paths';
import { buildDotnetEnv, resolveDotnetExecutable } from './dotnet-runtime';
import {
buildPlaywrightRuntimeEnv,
ensureYinianPlaywrightRuntimeDirs,
resolveYinianPlaywrightBrowsersPath,
} from './playwright-runtime';
export type OfficeRuntimeStatus = 'ok' | 'warning' | 'error';
@@ -45,6 +50,15 @@ export interface OfficeSkillRuntimeDiagnostics {
available: boolean;
version: string | null;
};
playwright: {
moduleInstalled: boolean;
moduleName: string | null;
moduleResolvedPath: string | null;
browsersPath: string;
chromiumExecutablePath: string | null;
chromiumInstalled: boolean;
error: string | null;
};
checks: OfficeRuntimeCheck[];
}
@@ -74,6 +88,8 @@ const NODE_MODULES = [
'sharp',
] as const;
const PLAYWRIGHT_MODULE_CANDIDATES = ['playwright', 'playwright-core'] as const;
const PYTHON_INSTALL_TIMEOUT_MS = 10 * 60_000;
const COMMAND_TIMEOUT_MS = 20_000;
@@ -194,6 +210,66 @@ function resolveNodeModule(name: string): string | null {
return null;
}
function checkPlaywrightRuntime(): OfficeSkillRuntimeDiagnostics['playwright'] {
const runtimeEnv = buildPlaywrightRuntimeEnv();
const browsersPath = runtimeEnv.PLAYWRIGHT_BROWSERS_PATH || resolveYinianPlaywrightBrowsersPath();
const previousBrowsersPath = process.env.PLAYWRIGHT_BROWSERS_PATH;
process.env.PLAYWRIGHT_BROWSERS_PATH = browsersPath;
try {
for (const req of buildRequireCandidates()) {
for (const moduleName of PLAYWRIGHT_MODULE_CANDIDATES) {
try {
const moduleResolvedPath = req.resolve(moduleName);
const loaded = req(moduleName) as {
chromium?: {
executablePath?: () => string;
};
};
const chromiumExecutablePath = loaded.chromium?.executablePath?.() ?? null;
return {
moduleInstalled: true,
moduleName,
moduleResolvedPath,
browsersPath,
chromiumExecutablePath,
chromiumInstalled: Boolean(chromiumExecutablePath && existsSync(chromiumExecutablePath)),
error: null,
};
} catch {
// Try the next module/root.
}
}
}
return {
moduleInstalled: false,
moduleName: null,
moduleResolvedPath: null,
browsersPath,
chromiumExecutablePath: null,
chromiumInstalled: false,
error: 'Playwright runtime module not found',
};
} catch (error) {
return {
moduleInstalled: false,
moduleName: null,
moduleResolvedPath: null,
browsersPath,
chromiumExecutablePath: null,
chromiumInstalled: false,
error: error instanceof Error ? error.message : String(error),
};
} finally {
if (previousBrowsersPath === undefined) {
delete process.env.PLAYWRIGHT_BROWSERS_PATH;
} else {
process.env.PLAYWRIGHT_BROWSERS_PATH = previousBrowsersPath;
}
}
}
async function checkPythonPackage(pythonPath: string | null, importName: string): Promise<boolean> {
if (!pythonPath) return false;
const result = await runCommand(pythonPath, ['-c', `__import__(${JSON.stringify(importName)})`]);
@@ -245,6 +321,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
if (repairAttempted) {
await setupManagedPython();
ensureYinianPlaywrightRuntimeDirs();
}
let pythonPath = await findManagedPythonPath();
@@ -281,6 +358,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
};
});
const dotnet = await checkDotnet();
const playwright = checkPlaywrightRuntime();
const missingPythonPackages = packageResults.filter((pkg) => !pkg.installed);
const missingNodeModules = nodeModules.filter((mod) => !mod.installed);
@@ -315,6 +393,16 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
? `.NET ${dotnet.version}`
: '未找到 .NETdocx 高级 OpenXML 生成可能受限',
},
{
id: 'playwright-chromium',
label: '幻灯片浏览器预览',
status: playwright.moduleInstalled && playwright.chromiumInstalled ? 'ok' : 'warning',
detail: playwright.moduleInstalled
? (playwright.chromiumInstalled
? `Chromium 可用:${playwright.chromiumExecutablePath}`
: `未找到 Chromium 二进制文件;浏览器预览应跳过,不要在任务中运行 npx playwright install chromium。缓存路径${playwright.browsersPath}`)
: `未找到 Playwright 运行时;浏览器预览应跳过。${playwright.error ?? ''}`,
},
];
return {
@@ -329,6 +417,7 @@ export async function buildOfficeSkillRuntimeDiagnostics(options: {
modules: nodeModules,
},
dotnet,
playwright,
checks,
};
}

View File

@@ -0,0 +1,74 @@
import { existsSync, mkdirSync, readdirSync, symlinkSync } from 'node:fs';
import { homedir } from 'node:os';
import path from 'node:path';
import { getOpenClawConfigDir } from './paths';
const PLAYWRIGHT_BROWSERS_DIR = 'ms-playwright';
const PLAYWRIGHT_LOCKS_DIR = 'locks';
const PLAYWRIGHT_COMPLETE_MARKER = 'INSTALLATION_COMPLETE';
function resolveDefaultPlaywrightBrowsersPath(): string | null {
if (process.platform === 'darwin') {
return path.join(homedir(), 'Library', 'Caches', 'ms-playwright');
}
if (process.platform === 'win32') {
const localAppData = process.env.LOCALAPPDATA?.trim();
return localAppData ? path.join(localAppData, 'ms-playwright') : null;
}
return path.join(homedir(), '.cache', 'ms-playwright');
}
export function resolveYinianPlaywrightBrowsersPath(): string {
return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_BROWSERS_DIR);
}
export function resolveYinianPlaywrightInstallLockPath(): string {
return path.join(getOpenClawConfigDir(), 'runtime', PLAYWRIGHT_LOCKS_DIR, 'playwright-chromium-install.lock');
}
function mirrorCompletedDefaultBrowsers(targetRoot: string): void {
const defaultRoot = resolveDefaultPlaywrightBrowsersPath();
if (!defaultRoot || defaultRoot === targetRoot || !existsSync(defaultRoot)) return;
let entries;
try {
entries = readdirSync(defaultRoot, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
if (!entry.isDirectory() || !entry.name.startsWith('chromium')) continue;
const sourceDir = path.join(defaultRoot, entry.name);
if (!existsSync(path.join(sourceDir, PLAYWRIGHT_COMPLETE_MARKER))) continue;
const targetDir = path.join(targetRoot, entry.name);
if (existsSync(targetDir)) continue;
try {
symlinkSync(sourceDir, targetDir, process.platform === 'win32' ? 'junction' : 'dir');
} catch {
// Best effort. A missing browser remains a warning-level diagnostic.
}
}
}
export function ensureYinianPlaywrightRuntimeDirs(): void {
const browsersPath = resolveYinianPlaywrightBrowsersPath();
mkdirSync(browsersPath, { recursive: true });
mkdirSync(path.dirname(resolveYinianPlaywrightInstallLockPath()), { recursive: true });
mirrorCompletedDefaultBrowsers(browsersPath);
}
export function buildPlaywrightRuntimeEnv(
baseEnv: Record<string, string | undefined> = process.env as Record<string, string | undefined>,
): Record<string, string> {
const explicitBrowsersPath = baseEnv.PLAYWRIGHT_BROWSERS_PATH?.trim();
return {
PLAYWRIGHT_BROWSERS_PATH: explicitBrowsersPath || resolveYinianPlaywrightBrowsersPath(),
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: baseEnv.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD ?? '1',
YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH: baseEnv.YINIAN_PLAYWRIGHT_INSTALL_LOCK_PATH
?? resolveYinianPlaywrightInstallLockPath(),
};
}