feat(server): webserver standalone mode#1665
Conversation
…architecture diagrams
…nd wire entry points
Add a dependency-injection abstraction over Electron-specific APIs so the standalone WebServer can run in pure Node.js / Docker without Electron. New files: - src/common/platform/IPlatformServices.ts – interface definitions - src/common/platform/index.ts – register/get singleton - src/common/platform/NodePlatformServices.ts – Node.js implementation - src/common/platform/ElectronPlatformServices.ts – Electron wrapper - src/common/platform/register-node.ts – side-effect entry for server.ts - src/common/platform/register-electron.ts – side-effect entry for process/index.ts - src/common/electronSafe.ts – null-safe shim (marked @internal) - tests/unit/platform/platformRegistry.test.ts - tests/unit/platform/NodePlatformServices.test.ts Migrated call sites: - appEnv.ts, utils.ts, initStorage.ts, staticRoutes.ts, McpProtocol.ts, extensions/constants.ts → getPlatformServices().paths.* - ForkTask.ts → getPlatformServices().worker.fork (drops utilityProcess) - CronService.ts → getPlatformServices().power.{preventSleep,allowSleep} - notificationBridge.ts → getPlatformServices().notification.send (removes Electron-only click/failed/close handlers and setMainWindow) Updated tests: appEnv, BaseAgentManagerDecouple, cronService, tray, applicationBridge, mainWindowLifecycle, vitest.setup
- conversionService: use electronSafe shim for BrowserWindow (allowed consumer) - authRoutes: update verifyQRTokenDirect import path to webuiQR module
Resolve conflicts in initStorage.ts and utils.ts: - utils.ts: keep platform DI imports (getPlatformServices) over direct electron import - initStorage.ts: adopt ASAR unpack redirect from main, use getPlatformServices().paths.isPackaged() instead of app.isPackaged
…rm services
- configureChromium: add app.setPath('userData') alongside setName, since
Electron 28+ no longer derives userData from app.getName() on macOS
- platform/index.ts: auto-register NodePlatformServices for utility processes
(process.type !== 'browser') where app is unavailable
- ElectronPlatformServices: inject DATA_DIR env var when forking utility
processes so NodePlatformServices resolves correct paths
- process/index.ts: restore configureChromium as first import so app name
and userData path are set before any other module initializes
- Add server npm scripts using build-server.mjs (dev/prod/remote variants) - Add scripts/build-server.mjs for standalone server build - Add tsx dev dependency for TypeScript execution - Add dist-server/ to .gitignore (build output) - Add design docs for server scripts feature
…tformServices
Rollup does not bundle dynamic require() calls — only static imports are
inlined. The previous require("./NodePlatformServices") in getPlatformServices()
left a dangling relative reference in the output chunk that failed at runtime
with MODULE_NOT_FOUND. Replace with a static import so Rollup inlines the
class into the shared chunk.
…e.ts Remove static import of getSystemDir from initStorage.ts in utils.ts to eliminate the circular dependency causing module initialization failure in esbuild-bundled server output. Add optional cacheDir parameter to copyFilesToDirectory instead; callers that need temp-file cleanup pass cacheDir explicitly.
…tion details - Fix pragma() Bun adapter: three-case logic (setter/getter-array/getter-scalar) - Fix BunSqliteDriver.run() to use db.run(sql, ...args) not db.query().run() - Document getDatabase() async migration scope (~80 callers, includes sync sites) - Acknowledge full exec() split scope: ~30+ call sites across migrations v1-v15 - Mark bun:sqlite as required external in build script (not optional) - Add IStatement.run() lastInsertRowid field - Add BunSqliteDriver test integration plan (bun test, test:bun script)
…ver mode - Add worker entry points (gemini, acp, codex, openclaw-gateway, nanobot) to build-server.mjs so BaseAgentManager can fork them via dist-server/<type>.js - Fix pipe.ts to support both Electron (process.parentPort) and Node.js child_process.fork (process.on/send) IPC — workers were silently receiving no messages in server mode - Add unhandledRejection handler in worker utils to prevent tree-sitter WASM stub errors from crashing the worker process - Switch worker builds to wasmRuntimePlugin: copy tree-sitter WASM files to dist-server/wasm/ and load them via fs.readFileSync at runtime, eliminating the CompileError/abort noise and enabling shell syntax parsing
Replace getUserSkillsDir() and findBuiltinResourceDir() with initStorage exports (getSkillsDir, getBuiltinSkillsDir) which already abstract the platform via getPlatformServices(). Fixes skills hub showing 0 skills in standalone server mode.
systemInfo, updateSystemInfo, getPath are now shared between Electron and standalone. Electron-only handlers (restart, devtools, zoom, CDP) remain in applicationBridge.ts.
resolveBuiltinDir("src/skills") only works for packaged Electron builds
because viteStaticCopy maps src/process/resources/skills/** → skills/.
In development and standalone server mode the path src/skills does not
exist, so existsSync returned false and the copy was silently skipped.
Add a fallback chain after resolveBuiltinDir:
- Development: src/process/resources/skills/ (actual source path)
- Standalone production: dist-server/skills/ (copied by build-server.mjs)
Also update build-server.mjs to copy src/process/resources/skills/ →
dist-server/skills/ so the skills are available alongside the bundle.
… to prevent crash Previously, getOrBuildTask was called with void before the conversation was saved to the database, causing an unhandled promise rejection that crashed the Bun process when changing workspace.
Resolved 8 conflicts: - src/process/bridge/index.ts: added missing initPptPreviewBridge import/call/export from v1.8.33 - src/process/bridge/conversationBridge.ts: kept HEAD fix (getOrBuildTask moved after createWithMigration) - src/process/bridge/fsBridge.ts: applied v1.8.33 error handling improvements (ENOENT→null for readFile/readFileBuffer, return '' for readBuiltinRule/readBuiltinSkill, try/catch for fetchRemoteImage, fs.mkdir before createZip writeFile) - src/process/task/AcpAgentManager.ts: kept HEAD (standalone branch uses direct presetContext injection, not prepareFirstMessageWithSkillsIndex) - src/process/utils/configureChromium.ts: kept HEAD (Electron 28+ specific userData path fix using dirname) - src/process/utils/initStorage.ts: applied v1.8.33 (src/skills→src/process/resources/skills dev mapping, morph-ppt preset enabledByDefault) - tests/unit/applicationBridge.test.ts: took v1.8.33 (simplified mocks with mockElectronApp helper, appData path support) - tests/unit/fsBridge.skills.test.ts: took v1.8.33 (new tests for ENOENT, createZip parent dir, readBuiltinRule/Skill, fetchRemoteImage error handling)
Replace electron app.getPath('userData') with getPlatformServices().paths.getDataDir()
to remove the Electron dependency, then register initPptPreviewBridge in initBridgeStandalone.
Design and plan for migrating AI provider/MCP config from Electron desktop app to standalone Node server on first startup, with optional manual import via IMPORT_CONFIG_FROM env var.
… for migration guard
…ron for correctness
…les for standalone mode
…n standalone mode - Initialize ExtensionRegistry and ChannelManager in server.ts main() so that channel plugins (Telegram, Lark, DingTalk) work in standalone server mode; previously enablePlugin always returned 'manager not initialized' - Add ChannelManager.shutdown() to standalone server shutdown sequence - Pass globalThis.fetch to grammY Bot constructor to avoid node-fetch@2 vs abort-controller AbortSignal instanceof mismatch in the bundled server build; native fetch (undici/Chromium) accepts abort-controller signals via duck-typing - Register NodePlatformServices in acp worker entry point so that module-level calls to getPlatformServices() do not throw on startup in Node.js (non-Electron)
- fsBridge.skills.test.ts: add getSkillsDir/getBuiltinSkillsDir to initStorage mock; update builtinBase path to match getBuiltinSkillsDir() - NodePlatformServices.test.ts: update assertions to match actual fallback paths (homedir/.aionui-server) and getAppPath (process.cwd()) - pptPreviewBridge.test.ts: mock @common/platform/index so checkForUpdate does not throw "Services not registered"
getUserExtensionsDir() and resolveStatesFile() were using
os.homedir() + getEnvAwareName('.aionui') directly, which caused
the standalone server and Electron dev app to share the same
~/.aionui-dev/extensions/ and extension-states.json paths.
Replace with getDataPath() which already handles per-environment
isolation: returns the CLI-safe symlink (~/.aionui-dev) on macOS
Electron and the isolated data dir (~/.aionui-server/aionui) in
standalone mode.
Code Review:feat(server): webserver standalone mode (#1665)变更概述本 PR 为 AionUi 引入独立 Web 服务器模式,允许不依赖 Electron 直接以 Docker 容器部署。核心改动包括:新增 方案评估结论:✅ 方案合理 架构分层清晰,DI 层的引入有效解耦了平台相关代码, 问题清单🟠 HIGH — Dockerfile 未设置
|
| # | 严重级别 | 文件 | 问题 |
|---|---|---|---|
| 1 | 🟠 HIGH | Dockerfile:29 |
DATA_DIR 未默认为 /data,Docker 部署数据不持久化 |
| 2 | 🔵 LOW | src/process/utils/configMigration.ts:103–169 |
6 处 no-await-in-loop lint 警告,需注释说明意图 |
| 3 | 🔵 LOW | src/common/platform/ElectronPlatformServices.ts:9 |
误触发 require-post-message-target-origin lint 警告 |
结论
本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。
Without a default, data was written to ~/.aionui-server inside the container instead of the declared VOLUME /data, causing data loss on container restart. Review follow-up for #1665
PR Fix 验证报告原始 PR: #1665
总结: ✅ 已修复 1 个 | ⏭️ 跳过 2 个 |
Summary
Changes
Core Architecture
feat(platform): IntroduceIPlatformServicesinterface andNodePlatformServices/ElectronPlatformServicesimplementationsfeat(database): AddISqliteDriver,BunSqliteDriver,BetterSqlite3Driver, andcreateDriverfactory with runtime detectionrefactor(database): MigrateAionUIDatabaseto static async factory; update allgetDatabase()call sitesStandalone Server
feat(server): Add standalone server entry point (src/server.ts) and Dockerfilefeat(server): Switch server runtime to Bun withbun:sqlitedriverfeat(server): Add standalone renderer build (vite.renderer.config.ts) without Electron dependencyfeat(server): Enable bridges in standalone mode:cronBridge,mcpBridge,notificationBridge,pptPreviewBridge,fsBridge,applicationBridgeCorefeat(server): ExtractinitBridgeStandalone— bridge init without Electron-only bridgesfeat(adapter): Add shared registry for WebSocket broadcasters and bridge emitterConfig Migration
feat(config): AddconfigMigrationmodule for Electron → server config importfeat(config): Wire config migration intoinitStoragestartup sequencefix(config): Guard migration to only run outside Electron (process.versions.electron)Bug Fixes
fix(storage): Guard Electron-only calls; supportDATA_DIRenv varfix(server): Fix blank page, cookie import, and data persistence in standalone modefix(server): Fix WebSocket upgrade, ACP detection, and renderer servingfix(server): Fix Gemini worker IPC and WASM loading in standalone modefix(utils): ReimplementhasElectronAppPathviaprocess.versions.electronTest Plan
bun run serverand verify renderer loads in browserbun run test— all unit tests passprek run --from-ref origin/main --to-ref HEAD— no lint/format issues