Skip to content

01 · 系统总览与前端 ​

架构剖析第一篇:系统拓扑、Electron 主进程、Vue 3 renderer 与 Bridge 的通信链路。 对应 ARCHITECTURE.md §1–§2。


§1 系统拓扑 ​

┌─────────────────────────────────────────────────────────────────┐
│  Electron 主进程 (main.js)                                       │
│  ├─ BrowserWindow (主窗口 / 弹出聊天 / QuickAsk / PhoneCall)      │
│  ├─ Tray 系统托盘 (隐藏不退出; 退出需菜单触发)                      │
│  ├─ globalShortcut / clipboard / protocol handler                │
│  ├─ autoUpdater (electron-updater)                               │
│  └─ spawn 子进程: python -m src.bridge.run                        │
└──────────────────────┬──────────────────────────────────────────┘
                       │ HTTPS/HTTP2, 127.0.0.1:18711 (默认)
┌──────────────────────┴──────────────────────────────────────────┐
│  Python Bridge (src/bridge/run.py → src/bridge/app.py)           │
│  ├─ hypercorn + HTTP/2 (自签证书 workspace/.bridge.crt)           │
│  ├─ FastAPI + 60+ 路由 (src/bridge/routes/*.py)                  │
│  ├─ APScheduler (cron / schedule / reminder)                     │
│  ├─ 本地小模型 (rewriter / decider / memorizer-writer)           │
│  └─ 工具系统 (browser / DH / multimodal / skill / …)             │
└──────────────────────┬──────────────────────────────────────────┘
                       │
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
   OpenAI 兼容 LLM   本地模型群     外部服务
   (BASE_URL/API_KEY) (bge-m3,     (GPT-SoVITS,
                    rewriter,      浏览器 CDP,
                    decider)       社交平台)

为什么 Windows-first:CUDA/Windows WDDM 对线程首触敏感;Chrome CDP 共享 Profile;Windows toast/AUMID 穿透勿扰。macOS 分支存在但不完整(src/base/auth/device.py 仅 macOS 有真实实现,Windows 只有 hostname fingerprint)。

§2 Electron 主进程(electron/electron/main.js) ​

2.1 进程模型 ​

main.js 是 Electron 主进程入口,require 关键模块:

javascript
const { startPythonBridge, stopPythonBridge, isBridgeOurChild,
        getBridgePort, getBridgeToken, onBridgePortChange } = require('./python')
const { mountTray, destroyTray } = require('./tray')
const { grabSelectedTextViaCopy } = require('./clipboard_grab')
const { createQuickAskWindow, getQuickAskWindow } = require('./quick_ask_window')
const { createPhoneCallWindow, getPhoneCallWindow } = require('./phone_call_window')
const { bridgeUrl, bridgeFetch } = require('./bridgeHttp')

窗口清单:

窗口变量用途
主窗口mainWindow主 Vue SPA
覆盖层overlayWindow屏幕覆盖
QuickAskquickAskWindow快捷键弹出提问
PhoneCallphoneCallWindow语音通话
聊天弹出chatWindowsByKey (Map)按键 "<agent>:<dialogue>" 去重
子窗口集合childWindows (Set)跟踪全部弹出,关闭时清

隐藏不退出:关闭主窗口 → isQuitting=false → event.preventDefault() → 隐藏窗口;退出只通过托盘菜单 "退出后台" 触发 app.quit()。首次隐藏弹 Notification 提示用户托盘位置,标记文件 .tray-hint-shown 防重复。

2.2 EPIPE 防护 ​

javascript
// main.js 顶部 IIFE:
for (const stream of [process.stdout, process.stderr]) {
  stream.on('error', () => {})              // 吞异步 EPIPE
  const orig = stream.write.bind(stream)
  stream.write = function (...args) {       // 包装同步 write
    try { return orig(...args) } catch { return false }
  }
}
process.on('uncaughtException', (e) => { if (isEpipe(e)) return; throw e })
process.on('unhandledRejection', (e) => { if (isEpipe(e)) return; throw e })

父终端关闭时 stdout/stderr 触发 EPIPE → 三层防护(事件、同步、未捕获异常)。

§3 Bridge 生命周期(electron/electron/python.js) ​

3.1 端口决策 ​

javascript
const PORTFILE = path.join(PROJECT_ROOT, 'workspace', '.bridge.port')
const TOKENFILE = path.join(PROJECT_ROOT, 'workspace', '.bridge_token')
  1. 先检查 18711:isPortFree(18711) —— 若空闲,用默认端口(保持 autorun watchdog 稳定性)
  2. 若占用:pickFreePort() —— 绑定 127.0.0.1:0,OS 分配随机端口,关闭让出
  3. 健康探测:pingHealth(port, timeoutMs=2000) —— 轮询 /health,250ms 间隔
  4. 深度探测:deepProbe(port) —— 命中 FastAPI worker pool 端点,确认 asyncio 调度健康
  5. 写入端口文件:workspace/.bridge.port + workspace/.bridges.json

3.2 Python 解析 ​

javascript
function resolvePython(projectRoot) {
  if (process.env.FAMILIARS_PYTHON) return process.env.FAMILIARS_PYTHON
  const venv = process.platform === 'win32'
    ? path.join(projectRoot, '.venv', 'Scripts', 'python.exe')
    : path.join(projectRoot, '.venv', 'bin', 'python')
  if (fs.existsSync(venv)) return venv
  return process.platform === 'win32' ? 'python' : 'python3'
}

优先级:FAMILIARS_PYTHON 环境变量 > .venv/Scripts/python.exe > 系统 python。

3.3 启动参数 ​

python -m src.bridge.run

环境变量:

  • BRIDGE_PORT(默认 18711)
  • FAMILIARS_HTTP_INSECURE=1 → 回退到 uvicorn + HTTP
  • FAMILIARS_DAEMON=1 → autorun 模式(pidfile 锁 + 日志重定向)

§4 Vue 3 Renderer(electron/renderer/) ​

4.1 技术栈 ​

  • Vue 3 + TypeScript + Pinia
  • Vite 构建(dev 服务器 http://localhost:5173)
  • VueFlow(流程图编辑器)
  • axios(HTTP + SSE,preload 暴露 window.familiars API)

4.2 目录结构 ​

electron/renderer/src/
├── assets/                   # 静态资源 (mascot PNG 等)
├── components/
│   ├── Desktop.vue           # 新默认桌面壳 (manager landing)
│   ├── ManagerLanding.vue    # 旧 landing,localStorage.familiars:legacyLanding 激活
│   ├── ChatBubble.vue        # 聊天气泡 (含 emit_event 富渲染)
│   ├── CreativeWorkshop/     # 工坊 UI
│   ├── FlowEditor/           # 流编辑器 V2
│   ├── BrokerMarketplace/    # 市场 UI
│   └── Ventaris/             # 公会系统
├── stores/                   # Pinia stores
├── router/                   # Vue Router
└── main.ts                   # 入口

4.3 Renderer ↔ Bridge 通信 ​

renderer → preload → main.js → bridge
         ↓
    window.familiars.bridgeFetch(url, init)
         ↓
    bridgeUrl(port, path) → https://127.0.0.1:{port}{path}

CORS 白名单(src/bridge/app.py):

python
allow_origins=[
    "http://localhost:5173",     # Vite dev
    "http://127.0.0.1:5173",     # Vite alt
    "null",                       # file:// (packaged build)
]

Token 认证:X-Bridge-Token header(axios)或 ?token=... query(SSE/WS/<img src>)。豁免路径:/health、/api/auth/*、/api/broker_app/*(iframe 静态资产)、/api/demo/*、/api/chat/*(demo SPA 使用 demo token)。

4.4 端口热更新 ​

javascript
onBridgePortChange((newPort, newToken) => {
  // 重新配置 bridgeFetch 的 base URL
  // 重新注入 preload 的 token
})

当 bridge 重启换端口时,python.js 触发 _portChangeListeners → renderer 热切换。

§5 关键设计决策 ​

决策原因
单进程 bridge内存共享、无 IPC 复杂度、CUDA 首触线程安全
HTTPS/HTTP2自签证书 workspace/.bridge.crt(src/bridge/certs.py);HTTP/2 多路复用解决 Chromium 6-socket/host 限制
Electron 而非 Tauri复用系统 API(托盘/自启动/浏览器共享 Profile)、桌面限制少
不套 LangChain/AutoGen自研管线把「规划→工具→记忆→报告」作一等公民;本地小模型深度耦合
端口文件而非硬编码autorun watchdog 始终用 18711;Electron 探测后选端口;CLI 读端口文件

下一篇:02-Bridge 层 →

Familiars · 多 Agent AI 桌面应用