feat: prepare Zhinian desktop pilot
Some checks failed
Electron E2E / Electron E2E (macos-latest) (push) Has been cancelled
Electron E2E / Electron E2E (ubuntu-latest) (push) Has been cancelled
Electron E2E / Electron E2E (windows-latest) (push) Has been cancelled

This commit is contained in:
inman
2026-05-07 21:49:20 +08:00
parent cddaf37016
commit 0abc48189c
103 changed files with 10975 additions and 2049 deletions

View File

@@ -6,6 +6,7 @@ import { createRequire } from 'node:module';
import { dirname, isAbsolute, join, normalize } from 'path';
import { homedir } from 'os';
import { cpSync, existsSync, mkdirSync, readFileSync, realpathSync, rmSync } from 'fs';
import { cleanupOpenClawRuntimeNativeClipboard } from './optional-native-cleanup';
const require = createRequire(import.meta.url);
@@ -33,6 +34,8 @@ const REQUIRED_OPENCLAW_CONTEXT_MODULES = [
'qrcode-terminal/vendor/QRCode/index.js',
'qrcode-terminal/vendor/QRCode/QRErrorCorrectLevel.js',
] as const;
const YINIAN_OPENCLAW_RUNTIME_PATCH_MARKER = '.yinian-runtime-patch.json';
const YINIAN_OPENCLAW_RUNTIME_PATCH_VERSION = '2026-05-07-desktop-fast-chat-v1';
export {
quoteForCmd,
@@ -172,6 +175,25 @@ function hasRequiredOpenClawContextModules(dir: string): boolean {
}
}
function readYinianOpenClawRuntimePatchVersion(dir: string): string | undefined {
try {
const markerPath = join(dir, YINIAN_OPENCLAW_RUNTIME_PATCH_MARKER);
if (!existsSync(fsPath(markerPath))) return undefined;
const marker = JSON.parse(readFileSync(fsPath(markerPath), 'utf-8')) as { version?: string };
return marker.version;
} catch {
return undefined;
}
}
function hasYinianOpenClawRuntimePatch(dir: string): boolean {
return readYinianOpenClawRuntimePatchVersion(dir) === YINIAN_OPENCLAW_RUNTIME_PATCH_VERSION;
}
function hasPackagedOpenClawRuntimeRequirements(dir: string): boolean {
return hasRequiredOpenClawContextModules(dir) && hasYinianOpenClawRuntimePatch(dir);
}
function samePath(left: string, right: string): boolean {
try {
return realpathSync(fsPath(left)) === realpathSync(fsPath(right));
@@ -253,10 +275,12 @@ function findExternalOpenClawDir(excludedDirs: string[]): string | null {
seen.add(candidate);
if (excludedDirs.some((excluded) => samePath(candidate, excluded))) continue;
if (!isValidOpenClawPackageDir(candidate)) continue;
if (hasRequiredOpenClawContextModules(candidate)) return candidate;
logOpenClawRuntime('[openclaw-runtime] Ignoring external OpenClaw installation because required app dependencies are missing', {
if (hasPackagedOpenClawRuntimeRequirements(candidate)) return candidate;
logOpenClawRuntime('[openclaw-runtime] Ignoring external OpenClaw installation because it is not a patched Yinian runtime', {
candidate,
requiredModules: REQUIRED_OPENCLAW_CONTEXT_MODULES,
runtimePatchVersion: readYinianOpenClawRuntimePatchVersion(candidate) ?? null,
expectedRuntimePatchVersion: YINIAN_OPENCLAW_RUNTIME_PATCH_VERSION,
});
}
@@ -271,7 +295,7 @@ function installBundledOpenClawToManagedRuntime(bundledDir: string, managedDir:
? readOpenClawVersion(managedDir)
: undefined;
if (managedVersion && bundledVersion && managedVersion === bundledVersion && hasRequiredOpenClawContextModules(managedDir)) {
if (managedVersion && bundledVersion && managedVersion === bundledVersion && hasPackagedOpenClawRuntimeRequirements(managedDir)) {
return false;
}
@@ -279,6 +303,12 @@ function installBundledOpenClawToManagedRuntime(bundledDir: string, managedDir:
rmSync(fsPath(tempDir), { recursive: true, force: true });
mkdirSync(fsPath(dirname(tempDir)), { recursive: true });
cpSync(fsPath(bundledDir), fsPath(tempDir), { recursive: true, dereference: true });
const removedClipboardPackages = cleanupOpenClawRuntimeNativeClipboard(tempDir);
if (removedClipboardPackages > 0) {
logOpenClawRuntime('[openclaw-runtime] Removed optional native clipboard packages from managed runtime', {
removedClipboardPackages,
});
}
rmSync(fsPath(managedDir), { recursive: true, force: true });
cpSync(fsPath(tempDir), fsPath(managedDir), { recursive: true, dereference: true });
rmSync(fsPath(tempDir), { recursive: true, force: true });
@@ -303,7 +333,7 @@ function resolveOpenClawRuntime(): OpenClawRuntimeResolution {
return cachedOpenClawRuntime;
}
if (hasRequiredOpenClawContextModules(managedDir)) {
if (hasPackagedOpenClawRuntimeRequirements(managedDir)) {
cachedOpenClawRuntime = {
dir: managedDir,
source: 'managed',
@@ -337,7 +367,7 @@ function resolveOpenClawRuntime(): OpenClawRuntimeResolution {
}
}
if (hasRequiredOpenClawContextModules(managedDir)) {
if (hasPackagedOpenClawRuntimeRequirements(managedDir)) {
cachedOpenClawRuntime = {
dir: managedDir,
source: 'managed',
@@ -356,9 +386,11 @@ function resolveOpenClawRuntime(): OpenClawRuntimeResolution {
}
if (isValidOpenClawPackageDir(managedDir)) {
logOpenClawRuntime('[openclaw-runtime] Ignoring managed OpenClaw runtime because required app dependencies are missing', {
logOpenClawRuntime('[openclaw-runtime] Ignoring managed OpenClaw runtime because it is not a patched Yinian runtime', {
managedDir,
requiredModules: REQUIRED_OPENCLAW_CONTEXT_MODULES,
runtimePatchVersion: readYinianOpenClawRuntimePatchVersion(managedDir) ?? null,
expectedRuntimePatchVersion: YINIAN_OPENCLAW_RUNTIME_PATCH_VERSION,
});
}
@@ -415,10 +447,11 @@ export function reinstallManagedOpenClawRuntime(): OpenClawRuntimeResolution {
installedFromBundled = installBundledOpenClawToManagedRuntime(bundledDir, managedDir);
}
const managedReady = hasPackagedOpenClawRuntimeRequirements(managedDir);
cachedOpenClawRuntime = {
dir: hasRequiredOpenClawContextModules(managedDir) ? managedDir : bundledDir,
source: hasRequiredOpenClawContextModules(managedDir) ? 'managed' : isValidOpenClawPackageDir(bundledDir) ? 'bundled' : 'missing',
version: readOpenClawVersion(hasRequiredOpenClawContextModules(managedDir) ? managedDir : bundledDir),
dir: managedReady ? managedDir : bundledDir,
source: managedReady ? 'managed' : isValidOpenClawPackageDir(bundledDir) ? 'bundled' : 'missing',
version: readOpenClawVersion(managedReady ? managedDir : bundledDir),
bundledDir,
managedDir,
installedFromBundled,