Files
qiming/qimingclaw/crates/agent-electron-client/docs/architecture/REF-OPENCLAW.md

1307 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
version: 2.3
last-updated: 2026-02-24
status: reference
---
# openclaw 架构参考文档
> 本文档分析 openclaw AI 助手项目的优秀架构设计,为 Qiming Agent Electron 客户端提供改进参考。
>
> **源仓库**: https://github.com/openclaw/openclaw
> **创建日期**: 2026-02-24
---
## 目录
1. [项目概述](#1-项目概述)
2. [架构对比](#2-架构对比)
3. [可借鉴的设计模式](#3-可借鉴的设计模式)
4. [具体改进建议](#4-具体改进建议)
5. [实施路线图](#5-实施路线图)
---
## 1. 项目概述
### 1.1 openclaw 项目
- **仓库地址**: https://github.com/openclaw/openclaw
- **语言**: TypeScript (ESM)
- **运行时**: Node.js 22+ / Bun
- **类型**: 个人 AI 助手平台
- **架构特点**: 多通道消息集成 + 插件化架构
**核心功能**:
- 多通道消息集成 (Telegram, Slack, Discord, WhatsApp, Signal, iMessage, Google Chat 等)
- Gateway 网关服务 (WebSocket 控制平面)
- 技能系统 (Skills) 和插件扩展
- 跨平台客户端 (macOS/iOS/Android)
- AI Agent 运行时 (Pi Agent Core)
### 1.2 Qiming Agent Electron 项目
- **路径**: `/Users/apple/workspace/qiming-agent/crates/agent-electron-client`
- **语言**: TypeScript / Rust (核心层)
- **运行时**: Electron / Node.js
- **类型**: AI Agent 桌面客户端
- **架构特点**: Electron 双进程 + 服务分层
**核心功能**:
- 多引擎 Agent 支持 (claude-code / qimingcode)
- ACP 协议集成
- 沙箱执行和权限管理
- MCP (Model Context Protocol) 支持
- IM 集成 (Telegram/Discord/DingTalk/Feishu)
---
## 2. 架构对比
### 2.1 整体架构
```
┌─────────────────────────────────────────────────────────────────────┐
│ openclaw │
├─────────────────────────────────────────────────────────────────────┤
│ CLI Layer (src/cli/) │
│ ├── build-program.ts (程序构建器) │
│ ├── command-registry.ts (命令注册) │
│ └── program-context.ts (上下文管理) │
│ ↓ │
│ Gateway Layer (src/gateway/) │
│ ├── server.impl.ts (WebSocket 服务器) │
│ ├── server-methods.ts (RPC 方法) │
│ ├── server-channels.ts (通道管理) │
│ └── server-plugins.ts (插件加载) │
│ ↓ │
│ Channel Layer (src/channels/) │
│ ├── plugins/ (通道插件) │
│ │ ├── telegram.ts │
│ │ ├── discord.ts │
│ │ ├── slack.ts │
│ │ └── ... │
│ └── channel-config.ts (配置解析) │
│ ↓ │
│ Agent Layer (src/agents/) │
│ ├── pi-embedded-runner/ (Pi 运行时) │
│ ├── skills/ (技能系统) │
│ └── subagent-registry.ts (子代理注册) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ Qiming-Agent Electron │
├─────────────────────────────────────────────────────────────────────┤
│ Electron Main Process │
│ ├── IPC Handlers (40+ handlers) │
│ │ └── agentHandlers.ts │
│ ├── Services │
│ │ ├── engines/unifiedAgent.ts │
│ │ ├── engines/acp/acpEngine.ts │
│ │ └── system/dependencies.ts │
│ └── DB (SQLite) │
│ ↓ IPC 通信 │
│ Electron Renderer Process │
│ ├── Components (React 18 + Ant Design) │
│ ├── Services │
│ │ ├── renderer/permissions.ts │
│ │ └── renderer/sandbox.ts │
│ └── State (useState) │
│ ↓ 调用 │
│ qiming-agent-core (Rust) │
└─────────────────────────────────────────────────────────────────────┘
```
### 2.2 核心模块对比
| 维度 | openclaw | Qiming-Agent Electron |
|------|----------|----------------------|
| **入口点** | `openclaw.mjs``buildProgram()` | `main.ts``App.tsx` |
| **命令系统** | `command-registry.ts` (动态注册) | IPC Handlers (分散) |
| **通道管理** | `channel-config.ts` (统一解析) | 无对应模块 |
| **事件系统** | Gateway WebSocket (集中式) | IPC + EventEmitter (分散) |
| **插件系统** | `plugins/registry.ts` (统一注册) | 无对应模块 |
| **配置管理** | Zod Schema 验证 | TypeScript 接口 |
---
## 3. 可借鉴的设计模式
### 3.1 命令注册系统
#### openclaw 实现
**源文件**: `src/cli/program/command-registry.ts:14-17, 40-205`
```typescript
// 命令注册类型定义
export type CommandRegistration = {
id: string;
register: (params: CommandRegisterParams) => void;
};
type CoreCliEntry = {
commands: CoreCliCommandDescriptor[];
register: (params: CommandRegisterParams) => Promise<void> | void;
};
// 核心命令注册表
const coreEntries: CoreCliEntry[] = [
{
commands: [
{
name: "setup",
description: "Initialize local config and agent workspace",
hasSubcommands: false,
},
],
register: async ({ program }) => {
const mod = await import("./register.setup.js");
mod.registerSetupCommand(program);
},
},
{
commands: [
{
name: "agent",
description: "Run one agent turn via the Gateway",
hasSubcommands: false,
},
{
name: "agents",
description: "Manage isolated agents (workspaces, auth, routing)",
hasSubcommands: true,
},
],
register: async ({ program, ctx }) => {
const mod = await import("./register.agent.js");
mod.registerAgentCommands(program, {
agentChannelOptions: ctx.agentChannelOptions,
});
},
},
// ... 更多命令
];
// 懒加载命令注册
function registerLazyCoreCommand(
program: Command,
ctx: ProgramContext,
entry: CoreCliEntry,
command: CoreCliCommandDescriptor,
) {
const placeholder = program.command(command.name).description(command.description);
placeholder.allowUnknownOption(true);
placeholder.action(async (...actionArgs) => {
removeEntryCommands(program, entry);
await entry.register({ program, ctx, argv: process.argv });
await reparseProgramFromActionArgs(program, actionArgs);
});
}
```
**设计要点**:
- 命令注册与实现分离
- 懒加载减少启动时间
- 统一的命令描述结构
- 支持子命令嵌套
#### Qiming Agent 现状
**源文件**: `src/main/ipc/agentHandlers.ts:7-323`
```typescript
// IPC 处理器直接定义
export function registerAgentHandlers(): void {
ipcMain.handle('agent:init', async (_, config: AgentConfig) => { ... });
ipcMain.handle('agent:prompt', async (_, sessionId, parts, opts) => { ... });
ipcMain.handle('agent:abort', async (_, sessionId) => { ... });
// ... 40+ handlers 散落在各处
}
```
**问题**:
- 无统一的命令/处理器注册机制
- 缺少元数据描述
- 难以实现懒加载
---
### 3.2 通道配置解析
#### openclaw 实现
**源文件**: `src/channels/channel-config.ts:60-164`
```typescript
// 通道匹配来源类型
export type ChannelMatchSource = "direct" | "parent" | "wildcard";
export type ChannelEntryMatch<T> = {
entry?: T;
key?: string;
wildcardEntry?: T;
wildcardKey?: string;
parentEntry?: T;
parentKey?: string;
matchKey?: string;
matchSource?: ChannelMatchSource;
};
// 解析通道配置(支持通配符和父级继承)
export function resolveChannelEntryMatchWithFallback<T>(params: {
entries?: Record<string, T>;
keys: string[];
parentKeys?: string[];
wildcardKey?: string;
normalizeKey?: (value: string) => string;
}): ChannelEntryMatch<T> {
// 1. 直接匹配
const direct = resolveChannelEntryMatch({
entries: params.entries,
keys: params.keys,
wildcardKey: params.wildcardKey,
});
if (direct.entry && direct.key) {
return { ...direct, matchKey: direct.key, matchSource: "direct" };
}
// 2. 标准化键匹配
const normalizeKey = params.normalizeKey;
if (normalizeKey) {
const normalizedKeys = params.keys.map((key) => normalizeKey(key)).filter(Boolean);
if (normalizedKeys.length > 0) {
for (const [entryKey, entry] of Object.entries(params.entries ?? {})) {
const normalizedEntry = normalizeKey(entryKey);
if (normalizedEntry && normalizedKeys.includes(normalizedEntry)) {
return {
...direct,
entry,
key: entryKey,
matchKey: entryKey,
matchSource: "direct",
};
}
}
}
}
// 3. 父级继承匹配
const parentKeys = params.parentKeys ?? [];
if (parentKeys.length > 0) {
const parent = resolveChannelEntryMatch({ entries: params.entries, keys: parentKeys });
if (parent.entry && parent.key) {
return {
...direct,
entry: parent.entry,
key: parent.key,
parentEntry: parent.entry,
parentKey: parent.key,
matchKey: parent.key,
matchSource: "parent",
};
}
}
// 4. 通配符匹配
if (direct.wildcardEntry && direct.wildcardKey) {
return {
...direct,
entry: direct.wildcardEntry,
key: direct.wildcardKey,
matchKey: direct.wildcardKey,
matchSource: "wildcard",
};
}
return direct;
}
```
**设计要点**:
- 多级匹配策略 (direct → normalize → parent → wildcard)
- 类型安全的配置解析
- 支持配置继承和覆盖
- 可追溯的匹配来源
---
### 3.3 Gateway 服务器架构
#### openclaw 实现
**源文件**: `src/gateway/server.impl.ts:1-150`
```typescript
export type GatewayServerOptions = {
/**
* Bind address policy for the Gateway WebSocket/HTTP server.
* - loopback: 127.0.0.1
* - lan: 0.0.0.0
* - tailnet: bind only to the Tailscale IPv4 address (100.64.0.0/10)
* - auto: prefer loopback, else LAN
*/
bind?: import("../config/config.js").GatewayBindMode;
host?: string;
controlUiEnabled?: boolean;
openAiChatCompletionsEnabled?: boolean;
openResponsesEnabled?: boolean;
auth?: import("../config/config.js").GatewayAuthConfig;
// ...
};
export type GatewayServer = {
close: (opts?: { reason?: string; restartExpectedMs?: number | null }) => Promise<void>;
};
export async function startGatewayServer(options: GatewayServerOptions): Promise<GatewayServer> {
// 1. 初始化配置和依赖
const deps = createDefaultDeps();
const config = await loadConfig();
// 2. 初始化插件系统
const pluginRegistry = createEmptyPluginRegistry();
// 3. 初始化通道管理器
const channelManager = await createChannelManager(/* ... */);
// 4. 初始化 WebSocket 服务器
const wss = new WebSocketServer({ /* ... */ });
// 5. 注册 RPC 方法
const methods = coreGatewayHandlers(/* ... */);
// 6. 启动健康监控
startChannelHealthMonitor(/* ... */);
// 7. 启动配置热重载
startGatewayConfigReloader(/* ... */);
// ...
}
```
**设计要点**:
- 模块化的服务器组件
- 清晰的生命周期管理
- 热重载支持
- 健康监控集成
---
### 3.4 程序构建器模式
#### openclaw 实现
**源文件**: `src/cli/program/build-program.ts:8-20`
```typescript
export function buildProgram() {
const program = new Command();
const ctx = createProgramContext();
const argv = process.argv;
// 设置程序上下文
setProgramContext(program, ctx);
// 配置帮助系统
configureProgramHelp(program, ctx);
// 注册预执行钩子
registerPreActionHooks(program, ctx.programVersion);
// 注册命令
registerProgramCommands(program, ctx, argv);
return program;
}
```
**说明**: 以下 `context` 结构为设计抽象,用于迁移思路说明;不是对 openclaw 某个具体文件的逐行引用。
```typescript
export type ProgramContext = {
programVersion: string;
agentChannelOptions: AgentChannelOptions;
// ...
};
export function createProgramContext(): ProgramContext {
return {
programVersion: readPackageVersion(),
agentChannelOptions: parseAgentChannelOptions(),
// ...
};
}
```
**设计要点**:
- 上下文与程序分离
- 可测试的构建流程
- 统一的配置入口
---
## 4. 具体改进建议
### 4.1 统一 IPC 处理器注册系统
#### 目标
参考 openclaw 的 `command-registry.ts`,建立统一的 IPC 处理器注册机制。
#### 建议实现
```typescript
// src/main/ipc/registry.ts
import { ipcMain, type IpcMainEvent, type IpcMainInvokeEvent } from 'electron';
import log from 'electron-log';
/**
* IPC 处理器注册类型
* 参考: openclaw/src/cli/program/command-registry.ts:14-17
*/
export type IpcHandlerEntry = {
id: string;
handlers: IpcHandlerDescriptor[];
load: (deps: IpcDependencies) => Promise<IpcHandlerModule>;
};
export type IpcHandlerDescriptor = {
channel: string;
description: string;
hasResponse: boolean; // true: ipcMain.handle, false: ipcMain.on
};
/**
* IPC 依赖上下文
*/
export type IpcDependencies = {
agentService: UnifiedAgentService;
db: Database;
configManager: ConfigManager;
// ...
};
export type IpcInvokeHandler = (event: IpcMainInvokeEvent, ...args: unknown[]) => Promise<unknown> | unknown;
export type IpcEventHandler = (event: IpcMainEvent, ...args: unknown[]) => Promise<void> | void;
export type IpcHandlerModule = {
invoke: Record<string, IpcInvokeHandler>;
events?: Record<string, IpcEventHandler>;
};
/**
* 核心 IPC 处理器注册表
* 参考: openclaw/src/cli/program/command-registry.ts:40-205
*/
const coreIpcEntries: IpcHandlerEntry[] = [
{
id: 'agent',
handlers: [
{
channel: 'agent:init',
description: 'Initialize agent service with config',
hasResponse: true,
},
{
channel: 'agent:destroy',
description: 'Destroy agent service',
hasResponse: true,
},
{
channel: 'agent:prompt',
description: 'Send prompt to agent (blocking)',
hasResponse: true,
},
{
channel: 'agent:promptAsync',
description: 'Send prompt to agent (non-blocking)',
hasResponse: true,
},
],
load: async ({ agentService }) => {
const mod = await import('./handlers/agent-handlers.js');
return mod.createAgentHandlers({ agentService });
},
},
{
id: 'session',
handlers: [
{
channel: 'session:create',
description: 'Create a new session',
hasResponse: true,
},
{
channel: 'session:list',
description: 'List all sessions',
hasResponse: true,
},
],
load: async ({ db }) => {
const mod = await import('./handlers/session-handlers.js');
return mod.createSessionHandlers({ db });
},
},
// ... 更多处理器组
];
const loadedModules = new Map<string, IpcHandlerModule>();
const loadingPromises = new Map<string, Promise<IpcHandlerModule>>();
async function ensureEntryLoaded(entry: IpcHandlerEntry, deps: IpcDependencies): Promise<IpcHandlerModule> {
const loaded = loadedModules.get(entry.id);
if (loaded) return loaded;
const loading = loadingPromises.get(entry.id);
if (loading) return loading;
const promise = entry
.load(deps)
.then((mod) => {
loadedModules.set(entry.id, mod);
return mod;
})
.finally(() => {
loadingPromises.delete(entry.id);
});
loadingPromises.set(entry.id, promise);
return promise;
}
/**
* 懒加载 IPC 处理器注册
* 参考: openclaw/src/cli/program/command-registry.ts:241-255
*/
function registerLazyIpcHandler(
entry: IpcHandlerEntry,
deps: IpcDependencies,
ipc: typeof ipcMain,
) {
// 注册稳定的代理处理器,首次调用时加载真实模块。
// 避免在回调内重复 ipcMain.handle(),防止重复注册和语义错误。
for (const handler of entry.handlers) {
if (handler.hasResponse) {
ipc.handle(handler.channel, async (event, ...args) => {
const mod = await ensureEntryLoaded(entry, deps);
const fn = mod.invoke[handler.channel];
if (!fn) {
throw new Error(`[IPC] Missing invoke handler: ${handler.channel} (entry: ${entry.id})`);
}
return fn(event, ...args);
});
} else {
ipc.on(handler.channel, async (event, ...args) => {
try {
const mod = await ensureEntryLoaded(entry, deps);
const fn = mod.events?.[handler.channel];
if (!fn) {
throw new Error(`[IPC] Missing event handler: ${handler.channel} (entry: ${entry.id})`);
}
await fn(event, ...args);
} catch (error) {
log.error('[IPC] Lazy event handler failed:', handler.channel, error);
}
});
}
}
}
/**
* 构建所有 IPC 处理器
*/
export function buildIpcHandlers(deps: IpcDependencies) {
for (const entry of coreIpcEntries) {
registerLazyIpcHandler(entry, deps, ipcMain);
}
}
/**
* 获取所有已注册的 IPC 通道描述
*/
export function getIpcHandlerDescriptions(): Array<{ channel: string; description: string }> {
return coreIpcEntries.flatMap((entry) =>
entry.handlers.map((h) => ({ channel: h.channel, description: h.description }))
);
}
```
---
### 4.2 配置解析系统
#### 目标
参考 openclaw 的 `channel-config.ts`,建立统一的配置解析机制。
#### 建议实现
```typescript
// src/services/common/config-resolver.ts
/**
* 配置匹配来源
* 参考: openclaw/src/channels/channel-config.ts:1-12
*/
export type ConfigMatchSource = "direct" | "parent" | "wildcard" | "default";
export type ConfigEntryMatch<T> = {
entry?: T;
key?: string;
wildcardEntry?: T;
wildcardKey?: string;
parentEntry?: T;
parentKey?: string;
defaultEntry?: T;
defaultKey?: string;
matchKey?: string;
matchSource?: ConfigMatchSource;
};
/**
* 解析配置项(支持多级匹配)
* 参考: openclaw/src/channels/channel-config.ts:82-164
*/
export function resolveConfigEntryMatch<T>(params: {
entries?: Record<string, T>;
keys: string[];
parentKeys?: string[];
wildcardKey?: string;
defaultKey?: string;
normalizeKey?: (value: string) => string;
}): ConfigEntryMatch<T> {
const entries = params.entries ?? {};
const match: ConfigEntryMatch<T> = {};
// 1. 直接匹配
for (const key of params.keys) {
if (Object.prototype.hasOwnProperty.call(entries, key)) {
match.entry = entries[key];
match.key = key;
match.matchKey = key;
match.matchSource = "direct";
return match;
}
}
// 2. 标准化键匹配
const normalizeKey = params.normalizeKey;
if (normalizeKey) {
const normalizedKeys = params.keys.map(normalizeKey).filter(Boolean);
for (const [entryKey, entry] of Object.entries(entries)) {
const normalizedEntry = normalizeKey(entryKey);
if (normalizedEntry && normalizedKeys.includes(normalizedEntry)) {
return {
...match,
entry,
key: entryKey,
matchKey: entryKey,
matchSource: "direct",
};
}
}
}
// 3. 父级继承
const parentKeys = params.parentKeys ?? [];
for (const key of parentKeys) {
if (Object.prototype.hasOwnProperty.call(entries, key)) {
return {
...match,
entry: entries[key],
key,
parentEntry: entries[key],
parentKey: key,
matchKey: key,
matchSource: "parent",
};
}
}
// 4. 通配符匹配
if (params.wildcardKey && Object.prototype.hasOwnProperty.call(entries, params.wildcardKey)) {
return {
...match,
entry: entries[params.wildcardKey],
key: params.wildcardKey,
wildcardEntry: entries[params.wildcardKey],
wildcardKey: params.wildcardKey,
matchKey: params.wildcardKey,
matchSource: "wildcard",
};
}
// 5. 默认值
if (params.defaultKey && Object.prototype.hasOwnProperty.call(entries, params.defaultKey)) {
return {
...match,
entry: entries[params.defaultKey],
key: params.defaultKey,
defaultEntry: entries[params.defaultKey],
defaultKey: params.defaultKey,
matchKey: params.defaultKey,
matchSource: "default",
};
}
return match;
}
/**
* 权限配置解析器
*/
export class PermissionConfigResolver {
private readonly entries: Record<string, PermissionConfig>;
constructor(config: AppConfig) {
this.entries = config.permissions ?? {};
}
resolve(sessionId: string, workspaceId: string): PermissionConfig {
const match = resolveConfigEntryMatch<PermissionConfig>({
entries: this.entries,
keys: [sessionId],
parentKeys: [workspaceId],
wildcardKey: "*",
defaultKey: "default",
});
return match.entry ?? this.getDefaultPermission();
}
private getDefaultPermission(): PermissionConfig {
return {
'tool:read': 'allow',
'tool:edit': 'prompt',
'command:bash': 'prompt',
'file:read': 'allow',
'file:write': 'prompt',
'network:http': 'prompt',
};
}
}
```
---
### 4.3 程序上下文模式
#### 目标
参考 openclaw 的 `build-program.ts``context.ts`,建立统一的程序上下文。
#### 建议实现
```typescript
// src/main/context.ts
import { app } from 'electron';
import * as path from 'path';
import { APP_DATA_DIR_NAME } from './services/constants';
/**
* 程序上下文
* 参考: openclaw/src/cli/program/build-program.ts:8-20
*/
export type ProgramContext = {
// 版本信息
version: string;
// 路径信息(必须与项目约束一致:~/.qimingclaw
paths: {
homeDir: string;
appDataDir: string;
dbPath: string;
logsDir: string;
};
// 服务实例
services: {
agent: UnifiedAgentService;
db: Database;
config: ConfigManager;
permissions: PermissionManager;
sandbox: SandboxManager;
};
// 运行时配置
runtime: {
isDev: boolean;
platform: NodeJS.Platform;
appPath: string;
packaged: boolean;
};
};
function resolveAppPaths() {
const homeDir = app.getPath('home');
const appDataDir = path.join(homeDir, APP_DATA_DIR_NAME);
return {
homeDir,
appDataDir,
dbPath: path.join(appDataDir, 'qimingclaw.db'),
logsDir: path.join(appDataDir, 'logs'),
};
}
/**
* 创建默认程序上下文
*/
export async function createProgramContext(): Promise<ProgramContext> {
const paths = resolveAppPaths();
// 初始化服务
const config = await ConfigManager.create(paths.appDataDir);
const db = await Database.create(paths.dbPath);
const permissions = new PermissionManager(config);
const sandbox = new SandboxManager(config);
const agent = new UnifiedAgentService();
return Object.freeze({
version: app.getVersion(),
paths,
services: {
agent,
db,
config,
permissions,
sandbox,
},
runtime: {
isDev: !app.isPackaged,
platform: process.platform,
appPath: app.getAppPath(),
packaged: app.isPackaged,
},
});
}
/**
* 说明:
* - ProgramContext 仅保存“稳定依赖”(路径、服务、运行时信息)
* - 会话态activeSessionId、UI 选择态)放到 SessionStore/Renderer state不放在全局上下文中
*/
class ProgramContextManager {
private context: ProgramContext | null = null;
async init(): Promise<ProgramContext> {
if (this.context) return this.context;
this.context = await createProgramContext();
return this.context;
}
get(): ProgramContext {
if (!this.context) {
throw new Error('Program context not initialized');
}
return this.context;
}
async dispose(): Promise<void> {
if (!this.context) return;
await this.context.services.agent.destroy().catch(() => undefined);
await this.context.services.db.close().catch(() => undefined);
this.context = null;
}
}
export const programContextManager = new ProgramContextManager();
```
---
### 4.4 统一事件系统
#### 目标
参考 openclaw 的 Gateway 事件系统,建立统一的事件总线。
#### 建议实现
```typescript
// src/services/common/event-bus.ts
/**
* 事件类型定义
*/
export enum EventType {
// Agent 事件
AGENT_INITIALIZED = 'agent:initialized',
AGENT_DESTROYED = 'agent:destroyed',
AGENT_ERROR = 'agent:error',
// Session 事件
SESSION_CREATED = 'session:created',
SESSION_UPDATED = 'session:updated',
SESSION_DELETED = 'session:deleted',
// Message 事件
MESSAGE_RECEIVED = 'message:received',
MESSAGE_UPDATED = 'message:updated',
MESSAGE_SENT = 'message:sent',
// Permission 事件
PERMISSION_REQUIRED = 'permission:required',
PERMISSION_GRANTED = 'permission:granted',
PERMISSION_DENIED = 'permission:denied',
}
/**
* 事件数据接口
*/
export interface EventData<T = unknown> {
type: EventType;
timestamp: number;
source?: string;
payload: T;
}
/**
* 事件监听器
*/
export type EventListener<T = unknown> = (event: EventData<T>) => void | Promise<void>;
/**
* 统一事件总线
* 参考: openclaw 的 Gateway WebSocket 事件系统
*/
export class EventBus {
private listeners: Map<EventType, Set<EventListener>> = new Map();
private eventQueue: EventData[] = [];
private isDraining: boolean = false;
private drainScheduled: boolean = false;
private maxQueueSize: number = 1000;
private static instance: EventBus;
static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
/**
* 订阅事件
*/
on<T>(type: EventType, listener: EventListener<T>): () => void {
if (!this.listeners.has(type)) {
this.listeners.set(type, new Set());
}
this.listeners.get(type)!.add(listener as EventListener);
// 返回取消订阅函数
return () => this.off(type, listener);
}
/**
* 取消订阅
*/
off<T>(type: EventType, listener: EventListener<T>): void {
this.listeners.get(type)?.delete(listener as EventListener);
}
/**
* 发送事件(立即)
*/
async emit<T>(type: EventType, payload: T, source?: string): Promise<void> {
const event: EventData<T> = {
type,
timestamp: Date.now(),
source,
payload,
};
const listeners = this.listeners.get(type);
if (!listeners || listeners.size === 0) {
return;
}
const settled = await Promise.allSettled(
[...listeners].map((listener) => Promise.resolve().then(() => listener(event)))
);
settled.forEach((result) => {
if (result.status === 'rejected') {
console.error(`[EventBus] Listener error for ${type}:`, result.reason);
}
});
}
/**
* 发送事件(同步 fire-and-forget
* 适用于不要求调用方等待监听器执行结果的场景
*/
emitNow<T>(type: EventType, payload: T, source?: string): void {
void this.emit(type, payload, source).catch((error) => {
console.error(`[EventBus] emitNow failed for ${type}:`, error);
});
}
private scheduleDrain(): void {
if (this.drainScheduled) return;
this.drainScheduled = true;
queueMicrotask(() => {
this.drainScheduled = false;
void this.drainQueue().catch((error) => {
console.error('[EventBus] drainQueue failed:', error);
});
});
}
private async drainQueue(maxMs: number = 100): Promise<number> {
if (this.isDraining || this.eventQueue.length === 0) {
return 0;
}
this.isDraining = true;
const startTime = Date.now();
let processed = 0;
try {
while (this.eventQueue.length > 0 && Date.now() - startTime < maxMs) {
const event = this.eventQueue.shift();
if (!event) continue;
await this.emit(event.type, event.payload, event.source);
processed++;
}
// 时间片耗尽但队列还有数据,安排下一轮,避免阻塞主循环
if (this.eventQueue.length > 0) {
this.scheduleDrain();
}
} finally {
this.isDraining = false;
}
return processed;
}
/**
* 发送异步事件(进入队列并自动调度处理)
*/
emitAsync<T>(type: EventType, payload: T, source?: string): void {
if (this.eventQueue.length >= this.maxQueueSize) {
console.warn('[EventBus] Event queue overflow, dropping oldest event');
this.eventQueue.shift();
}
this.eventQueue.push({
type,
timestamp: Date.now(),
source,
payload,
});
this.scheduleDrain();
}
/**
* 主动 flush用于测试或退出前确保队列处理完
*/
async flush(): Promise<void> {
while (this.eventQueue.length > 0 || this.isDraining) {
await this.drainQueue(100);
await new Promise((resolve) => setTimeout(resolve, 0));
}
}
/**
* 清空队列
*/
clearQueue(): void {
this.eventQueue = [];
}
/**
* 获取队列大小
*/
getQueueSize(): number {
return this.eventQueue.length;
}
}
// 全局实例
export const eventBus = EventBus.getInstance();
```
---
### 4.5 Lobster Runtime 集成openclaw + lobsterAI
#### 目标
在不替换现有 ACP 引擎层的前提下,引入 Lobster 的“可恢复工作流”能力(`run/resume`),用于长链路、可审计、可回放的任务执行。
#### 建议实现
```typescript
// src/main/services/workflow/lobsterRuntime.ts
export type LobsterRunConfig = {
workspaceDir: string;
prompt: string;
pipeline?: string;
profile: 'strict' | 'compat';
};
export type LobsterRunResult = {
runId: string;
status: 'running' | 'paused' | 'completed' | 'failed';
resumeToken?: string;
output?: string;
error?: string;
};
export interface LobsterRuntime {
run(config: LobsterRunConfig): Promise<LobsterRunResult>;
resume(runId: string, input?: string): Promise<LobsterRunResult>;
getStatus(runId: string): Promise<LobsterRunResult>;
}
```
```typescript
// src/main/ipc/handlers/lobster-handlers.ts
ipcMain.handle('lobster:run', async (_, config) => lobsterRuntime.run(config));
ipcMain.handle('lobster:resume', async (_, runId, input) => lobsterRuntime.resume(runId, input));
ipcMain.handle('lobster:status', async (_, runId) => lobsterRuntime.getStatus(runId));
```
```sql
-- SQLite: workflow_runs
CREATE TABLE IF NOT EXISTS workflow_runs (
id TEXT PRIMARY KEY,
engine TEXT NOT NULL, -- 'lobster'
profile TEXT NOT NULL, -- strict | compat
workspace_dir TEXT NOT NULL,
status TEXT NOT NULL, -- running | paused | completed | failed
resume_token TEXT,
pipeline_hash TEXT,
last_output TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
```
#### 环境变量策略(解决“过严限制导致动态安装失败”)
- `strict`(默认):维持当前隔离策略(`PATH/NODE_PATH/HOME/XDG` 受控),适用于生产稳定性和安全优先。
- `compat`(可切换):在 `strict` 基础上允许有限透传,降低动态安装/企业网络失败率。
- 必须透传:`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`, `SSL_CERT_FILE`, `NODE_EXTRA_CA_CERTS`
- 可选透传(按白名单配置):`PIP_INDEX_URL`, `UV_DEFAULT_INDEX`, `GIT_ASKPASS`, `SSH_AUTH_SOCK`
- 禁止透传(仍由应用控制):`CLAUDE_*`, `ANTHROPIC_*`, `OPENAI_*`, `NPM_CONFIG_PREFIX`
推荐实现:新增 `buildRuntimeEnv(profile, passthroughAllowlist)`,由 `dependencies.ts/getAppEnv()``acpClient.ts``mcp.ts` 统一调用,避免各模块各自拼接 env。
详细规则与迁移步骤见:
- `docs/architecture/RUNTIME-ENV-PROFILES.md`
---
## 5. 实施路线图
### 5.1 优先级矩阵
| 优先级 | 改进项 | 难度 | 预期价值 | 涉及文件 |
|--------|--------|------|----------|----------|
| 🔴 P0 | 统一 IPC 注册系统(稳定代理 + 一次加载) | 中 | 高 | `src/main/ipc/*` |
| 🔴 P0 | 程序上下文模式(路径约束对齐 `~/.qimingclaw` | 中 | 高 | `src/main/context.ts` |
| 🔴 P0 | 运行时环境双模式strict/compat | 中 | 高 | `src/main/services/system/*` |
| 🟡 P1 | 统一事件总线 | 中 | 高 | `src/services/common/event-bus.ts` |
| 🟡 P1 | Lobster Runtime 集成run/resume | 中 | 高 | `src/main/services/workflow/*` |
| 🟡 P1 | 配置解析系统 | 中 | 中 | `src/services/common/config-resolver.ts` |
| 🟢 P2 | 文档和类型定义 | 低 | 中 | `src/shared/types/*` |
### 5.2 实施步骤
#### Phase 1: 基础架构 (Week 1)
1. **创建程序上下文**
- 新建 `src/main/context.ts`
- 路径统一到 `app.getPath('home') + '/.qimingclaw'`
- 重构 `main.ts` 使用上下文
- 提取服务初始化逻辑
2. **统一 IPC 注册**
- 新建 `src/main/ipc/registry.ts`
- 使用“稳定代理 + 动态模块加载”模式,避免重复 `ipcMain.handle`
- 重构现有 handlers 使用注册表
- 添加处理器元数据
3. **环境双模式重构**
- 新建 `src/main/services/system/runtimeEnv.ts`
- 抽取 `strict/compat` 两套策略和白名单透传
- 统一替换 `dependencies.ts``acpClient.ts``mcp.ts` 的 env 拼接逻辑
#### Phase 2: 事件系统 (Week 2)
1. **实现事件总线**
- 新建 `src/services/common/event-bus.ts`
- 定义事件类型枚举
- `emit` 捕获异步监听器异常,`emitAsync` 自动调度 drain
- 重构 `unifiedAgent.ts` 使用事件总线
2. **配置解析**
- 新建 `src/services/common/config-resolver.ts`
- 实现多级匹配策略
- 重构权限配置使用解析器
3. **Lobster Runtime 集成**
- 新建 `src/main/services/workflow/lobsterRuntime.ts`
- 增加 `lobster:run/resume/status` IPC
- 新增 `workflow_runs` 表并接入状态持久化
#### Phase 3: 集成测试 (Week 3)
1. **添加单元测试**
2. **性能测试和优化**
3. **文档更新**
### 5.3 兼容性考虑
- 保持现有 IPC API 不变(使用适配器模式)
- 保持数据目录约束不变:所有持久化数据继续落在 `~/.qimingclaw/`
- 默认使用 `strict`,仅在显式配置下切换到 `compat`
- 分阶段迁移,不破坏现有功能
- 添加废弃警告,逐步过渡
---
## 附录
### A. openclaw 源码位置
| 模块 | 文件路径 |
|------|----------|
| 命令注册 | `src/cli/program/command-registry.ts` |
| 程序构建 | `src/cli/program/build-program.ts` |
| 通道配置 | `src/channels/channel-config.ts` |
| Gateway 服务器 | `src/gateway/server.impl.ts` |
| 插件系统 | `src/plugins/registry.ts` |
### B. Qiming Agent 源码位置
| 模块 | 文件路径 |
|------|----------|
| Agent Handlers | `src/main/ipc/agentHandlers.ts` |
| UnifiedAgentService | `src/main/services/engines/unifiedAgent.ts` |
| AcpEngine | `src/main/services/engines/acp/acpEngine.ts` |
| Permissions | `src/renderer/services/permissions.ts` |
| Sandbox | `src/renderer/services/sandbox.ts` |
### C. 参考资料
- [openclaw GitHub Repository](https://github.com/openclaw/openclaw)
- [Commander.js Documentation](https://github.com/tj/commander.js)
- [Electron IPC Documentation](https://www.electronjs.org/docs/latest/tutorial/ipc)
- `docs/architecture/RUNTIME-ENV-PROFILES.md`
---
*文档版本: 2.3*
*最后更新: 2026-02-24*