mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
397 lines
22 KiB
TypeScript
397 lines
22 KiB
TypeScript
import { antigravityHookService } from '../antigravity/hook-service'
|
|
import { getRelocatedDaemonHost } from '../daemon/daemon-host-relocation'
|
|
import { app, ipcMain, powerMonitor, session } from 'electron'
|
|
import { is } from '@electron-toolkit/utils'
|
|
import os from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { maybeRedirectCliLaunch } from './cli-launch-redirect'
|
|
import { runProfileStateRecoveryPreflight } from './profile-state-recovery-preflight'
|
|
import { argvRequestsServeMode, normalizeServeModeArgv } from './serve-mode-argv'
|
|
import {
|
|
configureDevUserDataPath,
|
|
configureElectronNetworkCompatibility,
|
|
configureOrcaUserDataPathEnv,
|
|
disableUnsupportedChromiumFeatures,
|
|
enableMainProcessGpuFeatures,
|
|
installDevParentDisconnectQuit,
|
|
installDevParentSignalQuit,
|
|
installDevParentWatchdog,
|
|
patchPackagedProcessPath,
|
|
optOutOfHiddenPageWakeUpThrottling
|
|
} from './configure-process'
|
|
import { installServeSupervisorDisconnectQuit } from '../serve-update-handoff'
|
|
import {
|
|
installUncaughtPipeErrorGuard,
|
|
installUnhandledRejectionLogging
|
|
} from './main-process-error-guards'
|
|
import { hydrateShellPath, mergePathSegments } from './hydrate-shell-path'
|
|
import { configureRemoteServerUpdater } from '../runtime/remote-server-updater'
|
|
import {
|
|
getRemoteServerUpdaterSnapshot,
|
|
checkForRemoteServerUpdate,
|
|
downloadRemoteServerUpdate,
|
|
installRemoteServerUpdate,
|
|
isQuittingForUpdate
|
|
} from '../updater'
|
|
import { getDevInstanceIdentity, shouldApplyPreReadyAppName } from './dev-instance-identity'
|
|
import { enableRendererHeapHeadroom } from './renderer-heap-headroom'
|
|
import { configureLinuxDevShmUsage } from './linux-dev-shm-policy'
|
|
import { isStartupDiagnosticsEnabled, logStartupDiagnostic } from './startup-diagnostics'
|
|
import { startEventLoopStallProbe } from './event-loop-stall-probe'
|
|
import { startEventLoopDelayProbe } from '../diagnostics/event-loop-delay-probe'
|
|
import {
|
|
isMainThreadDiagnosticsEnabled,
|
|
recordSubprocessSpawn,
|
|
startMainThreadChurnProbe
|
|
} from '../diagnostics/main-thread-churn-probe'
|
|
import { setSpawnObserver } from '@orca/process-host/spawn-observer'
|
|
import { settledDiffCache } from '../git/source-control/git-read-cache-invalidation'
|
|
import { reserveServeStdoutForReadiness } from '../server/serve-stdout-boundary'
|
|
import { createServeDesktopActivationGate } from './serve-desktop-activation'
|
|
import {
|
|
shouldBypassSingleInstanceLock,
|
|
shouldSkipSingleInstanceLock,
|
|
acquireSingleInstanceLock,
|
|
logSingleInstanceLockBypass,
|
|
logSingleInstanceLockFailure,
|
|
SINGLE_INSTANCE_ALREADY_RUNNING_EXIT_CODE
|
|
} from './single-instance-lock'
|
|
import { setAppEnvironment } from '../../shared/app-environment'
|
|
import { ElectronAppEnvironment } from '../host/electron-app-environment'
|
|
import { installMainProcessTreeKillGate } from '../own-chromium-tree-kill-guard'
|
|
import { setSecretStore } from '../../shared/secret-store'
|
|
import { ElectronSecretStore } from '../host/electron-secret-store'
|
|
import { selectLinuxKeyringBackend } from './select-linux-keyring-backend'
|
|
import { setPtyHostBindings } from '../ipc/pty-host-bindings'
|
|
import { electronRuntimeDesktopSurface } from '../host/electron-runtime-desktop-surface'
|
|
import { setRuntimeDesktopSurface } from '../runtime/runtime-desktop-surface'
|
|
import { installElectronBrowserCommands } from '../host/electron-browser-commands'
|
|
import { electronHttpClient } from '../host/electron-http-client'
|
|
import { setMainHttpClient } from '../network/http-client'
|
|
import { electronSpeechServiceFactories } from '../host/electron-speech-services'
|
|
import { setSpeechServiceFactories } from '../speech/speech-runtime-service'
|
|
import { setWorktreeWatcherRemoval } from '../ipc/worktree-watcher-removal'
|
|
import { desktopWorktreeWatcherRemoval } from '../ipc/filesystem-watcher'
|
|
import { setDefaultProxySessionResolver } from '../network/proxy-settings'
|
|
import { initDataPath, getCanonicalUserDataPath } from '../persistence'
|
|
import { applyMacPressAndHoldDefaultAtStartup } from '../macos-press-and-hold-default'
|
|
import { configureSessionParseCachePersistence } from '../ai-vault/session-parse-cache-persistence'
|
|
import { initOrcaProfilePaths } from '../orca-profiles/profile-index-store'
|
|
import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths'
|
|
import { recoverPendingProfileProjectMoves } from '../orca-profiles/profile-project-move-intent'
|
|
import { initStatsPath } from '../stats/collector'
|
|
import { initClaudeUsagePath } from '../claude-usage/store'
|
|
import { initCodexUsagePath } from '../codex-usage/store'
|
|
import { initOpenCodeUsagePath } from '../opencode-usage/store'
|
|
import { initMuseUsagePath } from '../muse-usage/store'
|
|
import { registerDocPreviewSchemePrivileges } from '../browser/doc-preview-protocol'
|
|
import { MEDIA_PREVIEW_CUSTOM_SCHEME } from '../media/media-preview-protocol'
|
|
import { JIRA_IMAGE_CUSTOM_SCHEME } from '../jira/attachments/image-protocol'
|
|
import { startCrashpadCapture } from '../crash-reporting/crashpad-capture'
|
|
import { CrashReportStore } from '../crash-reporting/crash-report-store'
|
|
import { recordCrashBreadcrumb } from '../crash-reporting/crash-breadcrumb-store'
|
|
import { getMainProcessLifecycleIdentity } from '../crash-reporting/main-process-lifecycle-identity'
|
|
import {
|
|
ensureVirtualDisplayForHeadlessServe,
|
|
hasUsableLinuxDisplay,
|
|
missingLinuxDisplayMessage
|
|
} from './ensure-virtual-display'
|
|
import {
|
|
createGpuCrashDiagnosticsRecorder,
|
|
maybeApplyGpuFallbackForThisLaunch,
|
|
registerGpuLifecycleHandlers
|
|
} from './gpu-lifecycle'
|
|
import { mainProcessState as state } from './main-process-state'
|
|
import { initializeSyntheticTitleRuntime } from './synthetic-title-runtime'
|
|
import { initializeBrowserProcessUserAgent } from '../browser/browser-process-user-agent'
|
|
import { initializeBrowserIdentityModeStore } from '../browser/browser-identity-mode-store'
|
|
import { acquireProfileStateRuntimeAdmission } from '../persistence/profile-state/profile-state-access'
|
|
import { getActiveProfileStateLocation } from '../persistence/profile-state/profile-state-active-location'
|
|
import {
|
|
acquireDesktopProfileLockOrExplain,
|
|
handleMainProcessPreflightFailure
|
|
} from './main-process-preflight-failure'
|
|
import { ensureWindowsAppDataPath } from './windows-app-data-path'
|
|
|
|
export type MainProcessPreflightOptions = {
|
|
focusExistingWindow: () => void
|
|
requestDesktopActivation: (argv?: readonly string[]) => void
|
|
}
|
|
|
|
/** Performs all module-scope work that must happen before Electron's ready event. */
|
|
export function runMainProcessPreflight(options: MainProcessPreflightOptions): boolean {
|
|
try {
|
|
return initializeMainProcessPreflight(options)
|
|
} catch (error) {
|
|
console.error('[startup] Preflight failed:', error)
|
|
handleMainProcessPreflightFailure(error)
|
|
return false
|
|
}
|
|
}
|
|
|
|
function initializeMainProcessPreflight(options: MainProcessPreflightOptions): boolean {
|
|
// Why first: every step below, recovery and the instance lock included, may resolve userData.
|
|
ensureWindowsAppDataPath(app)
|
|
if (runProfileStateRecoveryPreflight()) {
|
|
return false
|
|
}
|
|
// Why: on Windows a CLI launch that lost ELECTRON_RUN_AS_NODE would boot the GUI and exit silently; redirect to node mode before the lock gate below.
|
|
// The redirect runs before the serve-argv rewrite so it still matches on the launch argv verbatim.
|
|
// A direct Linux `serve` is exec'd into the CLI here (same pid); it only continues in-process if exec fails.
|
|
const cliLaunchRedirect = maybeRedirectCliLaunch({
|
|
isPackaged: app.isPackaged,
|
|
resourcesPath: process.resourcesPath,
|
|
execPath: process.execPath
|
|
})
|
|
if (cliLaunchRedirect.redirected) {
|
|
app.exit(cliLaunchRedirect.status)
|
|
}
|
|
// Why: extracted AppRun / binary launches can land CLI-form `serve` args on the
|
|
// Electron process without the CLI rewrite that injects `--serve` (#12677).
|
|
// Guarded so a normal GUI launch keeps its original argv array identity.
|
|
if (argvRequestsServeMode(process.argv)) {
|
|
process.argv = normalizeServeModeArgv(process.argv)
|
|
}
|
|
state.isServeMode = process.argv.includes('--serve')
|
|
// Fail before Chromium's missing-display teardown can segfault (#13719).
|
|
if (app.isPackaged && !state.isServeMode && !hasUsableLinuxDisplay()) {
|
|
process.stderr.write(`${missingLinuxDisplayMessage(false)}\n`)
|
|
app.exit(1)
|
|
}
|
|
if (state.isServeMode) {
|
|
reserveServeStdoutForReadiness()
|
|
}
|
|
state.devInstanceIdentity = getDevInstanceIdentity(is.dev)
|
|
state.devAgentHookEndpointNamespace = state.devInstanceIdentity.isDev
|
|
? state.devInstanceIdentity.appUserModelId
|
|
: undefined
|
|
state.desktopActivationGate = createServeDesktopActivationGate({
|
|
// Why held for desktop too: an activation before the startup window exists would open a
|
|
// second main window and abort launch; runtime launch releases it once that window exists.
|
|
initialState: 'initializing',
|
|
activateWindow: () => {
|
|
// Why: an updater replacement must not resurrect the old app bundle.
|
|
if (!isQuittingForUpdate()) {
|
|
options.focusExistingWindow()
|
|
}
|
|
},
|
|
onBlocked: (reason) => console.error(`[serve] Desktop activation blocked: ${reason}`)
|
|
})
|
|
installUncaughtPipeErrorGuard()
|
|
// Why (issue #9441): without this, one rejected background promise during startup restore kills main silently (exit 1, no crash report).
|
|
installUnhandledRejectionLogging()
|
|
// Why: expose the app version via process.env so main and the forked daemon can set TERM_PROGRAM_VERSION without importing electron.
|
|
process.env.ORCA_APP_VERSION = app.getVersion()
|
|
configureRemoteServerUpdater({
|
|
getSnapshot: getRemoteServerUpdaterSnapshot,
|
|
check: checkForRemoteServerUpdate,
|
|
download: downloadRemoteServerUpdate,
|
|
install: installRemoteServerUpdate
|
|
})
|
|
patchPackagedProcessPath()
|
|
// Why: the sync seed above covers early IPC (homebrew/nix); the async login-shell probe below (packaged only) then adds the user's rc PATH.
|
|
if (app.isPackaged && process.platform !== 'win32') {
|
|
void hydrateShellPath().then((result) => {
|
|
if (result.ok) {
|
|
mergePathSegments(result.segments)
|
|
} else {
|
|
// Why: on failure the seeded fallbacks stay in front. For an nvm user that is
|
|
// now their `default` version rather than the newest install, so it is usually
|
|
// survivable — but it is still not what their shell would have resolved. Name
|
|
// the reason so it shows up in a log bundle instead of as a missing CLI.
|
|
console.warn(
|
|
`[shell-path] login-shell probe failed (${result.failureReason}); using seeded PATH`
|
|
)
|
|
}
|
|
})
|
|
}
|
|
// Why before any spawn: `signalProcessTree` is shared with the CLI and relay, so
|
|
// it can only reach the main-process guard and breadcrumb store once this is registered.
|
|
installMainProcessTreeKillGate()
|
|
configureDevUserDataPath(is.dev)
|
|
configureOrcaUserDataPathEnv()
|
|
// Why these four lines are one step (#16761): the two above decide where userData lives, and
|
|
// everything below may resolve a path. Installing the accessor any later leaves a window where an
|
|
// early resolve either throws — which is what killed `orca serve` — or, worse, memoizes the
|
|
// pre-override directory and silently writes user state to the wrong place for the whole session.
|
|
// Safe this early: ElectronAppEnvironment holds no state and calls `app` lazily per accessor, so it
|
|
// changes no timing, and initDataPath only joins strings.
|
|
setAppEnvironment(new ElectronAppEnvironment())
|
|
// Why captured now: after the dev/E2E override above, and before app.setName('Orca') (whenReady)
|
|
// changes how userData resolves on a case-sensitive filesystem. See persistence.ts:20-28.
|
|
initDataPath()
|
|
startEventLoopDelayProbe('main', getCanonicalUserDataPath())
|
|
antigravityHookService.setWindowsRuntimePathProvider(
|
|
() => getRelocatedDaemonHost()?.execPath ?? process.execPath
|
|
)
|
|
|
|
// Why: Electron resolves the macOS safeStorage Keychain service name from the app name before
|
|
// ready. Dev pins userData above, so applying its name here cannot shift the captured path.
|
|
if (state.devInstanceIdentity && shouldApplyPreReadyAppName(state.devInstanceIdentity)) {
|
|
app.setName(state.devInstanceIdentity.appName)
|
|
}
|
|
state.startupDiagnosticsEnabled = isStartupDiagnosticsEnabled()
|
|
if (state.startupDiagnosticsEnabled) {
|
|
logStartupDiagnostic('before-single-instance-lock', {
|
|
version: app.getVersion(),
|
|
packaged: app.isPackaged,
|
|
platform: process.platform,
|
|
osRelease: os.release(),
|
|
userData: app.getPath('userData'),
|
|
e2eUserData: Boolean(process.env.ORCA_E2E_USER_DATA_DIR)
|
|
})
|
|
startEventLoopStallProbe()
|
|
}
|
|
// Self-gated on ORCA_MAIN_THREAD_DIAGNOSTICS; runs the whole session to catch steady-state churn (issue #7576).
|
|
// Why the diff-cache counters ride along: a stamp the filesystem reports unstably makes the cache
|
|
// look exactly like a cold start, and only the hit/miss/unprovable split tells the two apart.
|
|
if (isMainThreadDiagnosticsEnabled()) {
|
|
// Why here too: the probe's own call sites only cover src/main/git, so without
|
|
// this every spawnProcess/runProcess child (rg, ps, pty helpers) is invisible.
|
|
setSpawnObserver(recordSubprocessSpawn)
|
|
}
|
|
startMainThreadChurnProbe({ extraStats: () => ({ diffCache: settledDiffCache.stats() }) })
|
|
// Why: acquire AFTER configureDevUserDataPath — Electron derives lock identity from `userData`, so dev/packaged lock in separate namespaces.
|
|
// Why dev locks too: two processes on one profile corrupt its stores (PR #1326 / #1312); parallel `pnpm dev` needs ORCA_DEV_USER_DATA_PATH per copy.
|
|
const bypass = shouldBypassSingleInstanceLock({ isDev: is.dev, isServeMode: state.isServeMode })
|
|
const skip = shouldSkipSingleInstanceLock({ isDev: is.dev, isServeMode: state.isServeMode })
|
|
if (bypass) {
|
|
// Why: diagnostic escape hatch for macOS builds where Electron reports a false lock loss before any app logs exist.
|
|
logSingleInstanceLockBypass()
|
|
}
|
|
const hasLock = skip || bypass || acquireSingleInstanceLock(app, options.requestDesktopActivation)
|
|
if (state.startupDiagnosticsEnabled) {
|
|
logStartupDiagnostic('single-instance-lock-result', {
|
|
acquired: hasLock,
|
|
bypassed: bypass,
|
|
skippedForE2E: skip
|
|
})
|
|
}
|
|
if (!hasLock) {
|
|
// Why: a false-negative lock loss otherwise looks like a silent crash on packaged macOS; `open --stderr` can capture this line.
|
|
// In dev it is the line `pnpm dev` prints before exiting.
|
|
logSingleInstanceLockFailure({
|
|
isDevDesktop: is.dev && !state.isServeMode,
|
|
userDataPath: app.getPath('userData')
|
|
})
|
|
// Why: a graceful quit is deferred pre-ready, so this launch would still walk into Linux display init and SIGSEGV (#11935).
|
|
app.exit(SINGLE_INSTANCE_ALREADY_RUNNING_EXIT_CODE)
|
|
return false
|
|
}
|
|
// Why after Electron's lock: that one fences other desktops; this one fences orcad `orca serve`.
|
|
if (!skip && !bypass && !acquireDesktopProfileLockOrExplain(getCanonicalUserDataPath())) {
|
|
return false
|
|
}
|
|
state.profileStateAdmission = acquireProfileStateRuntimeAdmission(getCanonicalUserDataPath())
|
|
// Renderer and worker defaults must be fixed before any session exists.
|
|
initializeBrowserProcessUserAgent(
|
|
initializeBrowserIdentityModeStore(getCanonicalUserDataPath()).appliedMode
|
|
)
|
|
// Why first in this block: the accessor throws until installed and everything below may read a
|
|
// credential. The constructor does not touch `safeStorage` — it resolves lazily per call — so
|
|
// installing here changes no timing, in particular not the pre-ready Keychain service-name
|
|
// resolution. The app-environment port and the userData capture install earlier still, next to
|
|
// the path decision they depend on.
|
|
// Why immediately before the store is installed, and not later: Electron reads
|
|
// `--password-store` when it builds its os_crypt config during browser main parts,
|
|
// so a switch appended after that is ignored and the desktop keeps writing plaintext.
|
|
// Safe here — nothing above resolves a credential, and the probe inside is bounded.
|
|
selectLinuxKeyringBackend()
|
|
setSecretStore(new ElectronSecretStore())
|
|
// Why at process level, not per-window: pty.ts registers against injected surfaces so
|
|
// it can load without electron, and an Electron main process always has ipcMain —
|
|
// whether a window exists is irrelevant. Installing this in attachMainWindowServices
|
|
// meant `orca serve` registered its PTY handlers against no-ops before any window
|
|
// attached, so a paired desktop owner never received them.
|
|
setPtyHostBindings({ ipc: ipcMain, power: powerMonitor })
|
|
// Why also at process level: the runtime's notification, window-lookup and
|
|
// tab-create-reply channel are desktop-only. A Node host installs none and the
|
|
// runtime routes notifications to paired clients instead.
|
|
setRuntimeDesktopSurface(electronRuntimeDesktopSurface)
|
|
// Why here: constructing RuntimeBrowserCommands is what pulls the Chromium browser
|
|
// cluster into the graph. The desktop installs it; a Node host installs none and every
|
|
// browser RPC rejects, which capability filtering already tells clients about.
|
|
installElectronBrowserCommands()
|
|
// Why here: proxy-settings only needed electron for `session.defaultSession`. The
|
|
// desktop supplies it; a Node host has no Chromium proxy config to consult, so the
|
|
// environment variables are the whole answer there.
|
|
setDefaultProxySessionResolver(() => session.defaultSession)
|
|
// Why here: integrations use Chromium's network stack on the desktop. A Node host
|
|
// falls back to the platform default, which is a real behavioural difference (proxy
|
|
// read from the environment, Node's user agent) rather than a transparent swap.
|
|
setMainHttpClient(electronHttpClient)
|
|
// Why here: constructing the speech services is what pulls Electron's streaming net
|
|
// request in. A host without them rejects speech calls rather than pretending.
|
|
setSpeechServiceFactories(electronSpeechServiceFactories)
|
|
setWorktreeWatcherRemoval(desktopWorktreeWatcherRemoval)
|
|
// Why: couple to dev-parent only for electron-vite desktop runs; `orca serve`'s parent (CLI shim/background shell) isn't the intended server lifetime.
|
|
const shouldCoupleToDevParent = is.dev && !state.isServeMode
|
|
installDevParentDisconnectQuit(shouldCoupleToDevParent)
|
|
installDevParentWatchdog(shouldCoupleToDevParent)
|
|
installDevParentSignalQuit(shouldCoupleToDevParent)
|
|
// Why not at module scope with the other lifetime couplings (#16761): this resolves the handoff
|
|
// path, so it throws until setAppEnvironment() above installs the accessor — which killed every
|
|
// `orca serve` process before it could listen. After initDataPath() specifically, so the
|
|
// path-equality check against the CLI's env var uses the dir captured before app.setName().
|
|
// Safe to defer, and must stay synchronous: no 'disconnect' can be delivered until this module
|
|
// finishes evaluating, so moving this behind an await would open a real orphan window.
|
|
installServeSupervisorDisconnectQuit(state.isServeMode)
|
|
// Why here: initDataPath above gives the canonical userData path for the record file; the write
|
|
// itself lands for the next launch (see macos-press-and-hold-default.ts).
|
|
applyMacPressAndHoldDefaultAtStartup(getCanonicalUserDataPath())
|
|
// Why: use the canonical userData path — late app.getPath('userData') can resolve differently across restarts, defeating persistence.
|
|
configureSessionParseCachePersistence({
|
|
filePath: join(getCanonicalUserDataPath(), 'ai-vault', 'session-parse-cache.json'),
|
|
appVersion: app.getVersion()
|
|
})
|
|
initOrcaProfilePaths()
|
|
// A crash can leave a cross-profile SQLite move between its two commits. Resolve
|
|
// that journal before any Store opens a profile, so no reader observes a half-move.
|
|
const profileUserDataPath = getProfileUserDataPath()
|
|
recoverPendingProfileProjectMoves(
|
|
profileUserDataPath,
|
|
getActiveProfileStateLocation(profileUserDataPath)?.profileId
|
|
)
|
|
// Why: same timing as initDataPath — capture userData before app.setName changes it. See persistence.ts:20-28.
|
|
initStatsPath()
|
|
initClaudeUsagePath()
|
|
initCodexUsagePath()
|
|
initOpenCodeUsagePath()
|
|
initMuseUsagePath()
|
|
// Why: Electron freezes the privileged scheme table at ready, so the doc-preview
|
|
// scheme must be declared here or its webview loses fetch/secure-origin privileges.
|
|
registerDocPreviewSchemePrivileges([MEDIA_PREVIEW_CUSTOM_SCHEME, JIRA_IMAGE_CUSTOM_SCHEME])
|
|
// Why: must precede app.whenReady() so Crashpad is installed before the
|
|
// first renderer spawns; a CHECK before this point is still exit-code-only.
|
|
startCrashpadCapture()
|
|
state.crashReports = CrashReportStore.fromUserData()
|
|
state.gpuCrashDiagnostics = createGpuCrashDiagnosticsRecorder()
|
|
recordCrashBreadcrumb('app_started', {
|
|
packaged: app.isPackaged,
|
|
platform: process.platform,
|
|
...getMainProcessLifecycleIdentity()
|
|
})
|
|
disableUnsupportedChromiumFeatures()
|
|
// Why: unconditional — a GPU-fallback launch skips enableMainProcessGpuFeatures() below.
|
|
optOutOfHiddenPageWakeUpThrottling()
|
|
configureElectronNetworkCompatibility()
|
|
enableRendererHeapHeadroom()
|
|
configureLinuxDevShmUsage()
|
|
maybeApplyGpuFallbackForThisLaunch()
|
|
if (!state.gpuFallbackActiveThisLaunch) {
|
|
enableMainProcessGpuFeatures()
|
|
}
|
|
// Why: headless serve's offscreen BrowserWindows need an X display (Xvfb) on Linux; the result gates whether the offscreen backend is installed.
|
|
state.headlessBrowserDisplayAvailable = ensureVirtualDisplayForHeadlessServe({
|
|
isServeMode: state.isServeMode
|
|
})
|
|
// Why: continuing without Xvfb lets Ozone initialize without a display and SIGSEGV (#17615).
|
|
if (state.isServeMode && !state.headlessBrowserDisplayAvailable) {
|
|
process.stderr.write(`${missingLinuxDisplayMessage(true)}\n`)
|
|
app.exit(1)
|
|
}
|
|
initializeSyntheticTitleRuntime()
|
|
registerGpuLifecycleHandlers()
|
|
return true
|
|
}
|