OpenClaw 是一个大型 TypeScript/Node.js 项目,提供多渠道消息网关服务,支持 WhatsApp、Telegram、Discord、Slack、Signal、iMessage 等 15+ 即时通讯平台。项目采用现代化技术栈(Node 22+、TypeScript 5.9、Swift 6.2),包含约 500+ 源文件,横跨四种编程语言(TypeScript、Swift、Go、Shell),支持 macOS、iOS、Linux、Windows、Android 和树莓派等多平台部署。核心架构基于 Gateway 消息枢纽、插件系统和多 Agent 引擎,采用文件存储、无数据库依赖的轻量级设计。
架构与设计
代码质量
any 使用测试基础设施
安全性
依赖管理
技术债务
OpenClaw 是一个功能丰富的多平台消息网关和 AI 代理系统,采用现代化的 PNPM 工作区 monorepo 架构组织代码。该项目展示了如何在大型 TypeScript 项目中平衡模块化、构建效率和开发体验。本节将深入分析项目的整体结构、入口点设计、构建系统配置以及测试基础设施。
项目使用 PNPM 工作区管理多个相互依赖的包,工作区配置包含 ui/、packages/* 和 extensions/* 三个主要区域。核心源代码位于 /src 目录,包含约 500 个 TypeScript 文件,按功能模块划分为以下子目录:
cli/: 命令行界面实现,包括守护进程管理和用户交互gateway/: 消息网关服务器,支持 WebSocket 和 HTTP 协议channels/: 20+ 消息平台适配器(WhatsApp、Telegram、Discord、Slack、Signal 等)agents/: AI 代理执行引擎和子代理管理plugins/: 插件系统和钩子机制media-understanding/: 媒体内容理解和处理memory/: 对话历史和上下文存储hooks/: 15+ 生命周期钩子acp/: 代理控制协议实现infra/: 基础设施工具(日志、配置、环境变量)除核心源代码外,项目还包含以下顶层目录:
| 目录 | 用途 |
|---|---|
/apps |
原生应用(macOS 442 Swift 文件、iOS 45+ Swift 文件、Android Gradle 项目) |
/ui |
Web 界面和控制面板 |
/scripts |
50+ 自动化和构建脚本 |
/test |
测试套件和 E2E 基础设施 |
/docs |
Mintlify 文档(300+ Markdown 文件) |
/extensions |
可扩展插件包 |
配置文件层面,项目使用了现代化的工具链:tsconfig.json(TypeScript 配置)、tsdown.config.ts(打包配置)、多个 vitest.*.config.ts(测试配置)、.oxlintrc.json 和 .oxfmtrc.jsonc(Rust 基础的代码检查和格式化)、以及部署相关的 Dockerfile*、fly.toml、render.yaml。
OpenClaw 采用分层入口点设计,将 CLI 启动、运行时初始化、库导出和插件 SDK 清晰分离。这种设计使得项目既可以作为命令行工具使用,也可以作为库被其他项目引用。
openclaw.mjs)这是用户执行 openclaw 命令时的第一个入口点。该文件在 package.json 中通过 bin.openclaw 字段注册:
#!/usr/bin/env node
// openclaw.mjs - CLI 启动器
// 设置 Node.js 模块缓存路径
process.env.NODE_COMPILE_CACHE = '...';
// 配置警告过滤器(抑制实验性功能警告)
process.emitWarning = () => {};
// 加载编译后的入口点
import('./dist/entry.js');
设计考量:将启动逻辑与业务逻辑分离,使得 entry.js 可以独立测试,同时 .mjs 文件可以利用 Node.js 原生 ESM 支持。
src/entry.ts)运行时入口负责进程初始化和环境规范化:
// src/entry.ts - 运行时初始化
// 设置进程标题(在 ps/top 中显示)
process.title = 'openclaw';
// 规范化命令行参数
const normalizedArgv = normalizeArgv(process.argv);
// 如果需要,用 --disable-warning 标志重新启动进程
if (needsRespawn()) {
respawnWithFlags(['--disable-warning=ExperimentalWarning']);
}
// 加载主 CLI 逻辑
import('./cli').then(({ runCli }) => runCli(normalizedArgv));
重要行为:该入口点可能会 respawn(重新启动)当前进程以添加 Node.js 运行时标志。这是处理实验性功能警告的优雅方式,因为某些标志只能在进程启动时设置。
src/index.ts)当其他项目 import OpenClaw 时使用此入口:
// src/index.ts - 库导出
// 配置管理
export { loadConfig } from './config';
// 二进制依赖管理
export { ensureBinary } from './binaries';
// 执行引擎
export { runExec } from './exec';
// CLI 构建器(使用 Commander)
export { createCli } from './cli';
// 全局错误处理器注册
installGlobalErrorHandlers();
src/plugin-sdk/index.ts)为插件开发者提供稳定的 API 表面:
// src/plugin-sdk/index.ts
// 核心插件 API
export { definePlugin, PluginContext } from './core';
// 工具函数
export { registerHook, callHook } from './hooks';
// 子导出用于特殊功能
export * from './account-id';
插件 SDK 有独立的构建输出 (dist/plugin-sdk/),包含 TypeScript 类型声明文件,确保插件开发者获得完整的类型支持。
OpenClaw 使用 tsdown 作为打包工具,这是一个基于 Rolldown(Rust 实现的 Rollup 替代品)的现代构建工具。构建配置生成 8 个独立的 bundle:
| Bundle | 入口文件 | 用途 |
|---|---|---|
index.js |
src/index.ts |
库主入口 |
entry.js |
src/entry.ts |
CLI 运行时 |
daemon-cli.js |
src/cli/daemon-cli.ts |
守护进程 CLI |
warning-filter.js |
src/infra/warning-filter.ts |
警告过滤器 |
plugin-sdk/index.js |
src/plugin-sdk/index.ts |
插件 SDK |
plugin-sdk/account-id.js |
src/plugin-sdk/account-id.ts |
账户 ID 工具 |
extensionAPI.js |
src/extensionAPI.ts |
扩展 API |
hooks.js |
src/hooks/index.ts |
钩子系统 |
TypeScript 配置采用严格模式,目标为 ES2023,模块系统为 NodeNext:
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
构建流程(pnpm build)包含以下步骤:
.d.ts 文件项目使用 Vitest 作为测试框架,通过 6 个专门的配置文件支持不同类型的测试,这是大型项目中常见的最佳实践。
vitest.config.ts)所有测试配置的共享基础:
// vitest.config.ts
export default defineConfig({
test: {
pool: 'forks', // 进程隔离
testTimeout: 120_000, // 2 分钟超时
hookTimeout: 180_000, // Windows 上钩子超时更长
maxWorkers: process.env.CI ? 3 : 16, // CI 环境减少并发
minWorkers: process.env.CI ? 2 : 4,
unstubEnvs: true, // 测试间自动清理环境变量
unstubGlobals: true, // 测试间自动清理全局变量
setupFiles: ['test/setup.ts'],
coverage: {
provider: 'v8',
thresholds: {
lines: 70,
functions: 70,
statements: 70,
branches: 55
}
}
}
});
| 配置文件 | 用途 | 特殊设置 |
|---|---|---|
vitest.unit.config.ts |
快速单元测试 | 排除 extensions/ 和 gateway/ |
vitest.gateway.config.ts |
网关模块测试 | 仅包含 src/gateway/**/*.test.ts |
vitest.extensions.config.ts |
扩展测试 | 仅包含 extensions/**/*.test.ts |
vitest.e2e.config.ts |
端到端测试 | 使用 vmForks 池,更强隔离 |
vitest.live.config.ts |
实时集成测试 | 单 worker 串行执行,需要 OPENCLAW_LIVE_TEST=1 |
测试命令:
pnpm test # 并行测试运行器(自定义脚本)
pnpm test:fast # 仅单元测试
pnpm test:e2e # 端到端测试
pnpm test:live # 实时集成测试(访问真实外部服务)
pnpm test:coverage # 带覆盖率的单元测试
pnpm test:all # 完整测试套件 + Docker 测试
/scripts 目录包含 50+ 脚本,分为以下类别:
package-mac-app.sh、notarize-mac-artifact.sh(Apple 公证)、ios-team-id.shrun-openclaw-podman.sh、test-cleanup-docker.shrun-node.mjs(开发模式)、watch-node.mjs(文件监视)、test-parallel.mjs(并行测试)/scripts/e2e/)完整的 Docker 化 E2E 测试环境:
# 可用的 E2E 测试脚本
onboard-docker.sh # 用户引导流程测试
gateway-network-docker.sh # 网络配置测试
plugins-docker.sh # 插件系统测试
qr-import-docker.sh # 二维码导入测试
doctor-install-switch-docker.sh # 诊断命令测试
/scripts/docs-i18n/)基于 Go 的翻译工具链:
translator.go: 翻译引擎(使用 Claude Opus 4.5)markdown_segments.go: Markdown 解析和分段glossary.go: 术语一致性维护tm.go: 翻译记忆mobile-reauth.sh: 移动设备重新认证termux-*.sh: Android Termux 终端脚本(同步小部件、快速认证)ios-pull-gateway-log.sh: 从 iOS 设备拉取日志protocol-gen.ts: 生成协议 schema(JSON Schema)protocol-gen-swift.ts: Swift 协议代码生成release-check.ts: 发布前验证sync-plugin-versions.ts: 同步插件版本号优势:
挑战:
🔵 采用现代 PNPM Monorepo 架构 (info)
项目使用 PNPM 工作区管理多个相互依赖的包,核心源代码约 500 个 TypeScript 文件,按功能模块清晰划分
🔵 分层入口点设计 (info)
CLI、运行时、库导出、插件 SDK 四个入口点清晰分离,支持多种使用场景
🔵 使用 tsdown 现代打包工具 (info)
基于 Rolldown(Rust 实现)的打包工具,生成 8 个独立 bundle,支持 ES2023 和 NodeNext 模块
🔵 完善的 Vitest 测试分层 (info)
6 个专用配置文件支持单元、网关、扩展、E2E、Live 测试,CI 环境自适应 worker 数量
⚪ 脚本目录规模较大 (low)
50+ 自动化脚本分布在多个子目录,建议补充统一的 README 索引
// src/entry.ts
process.title = 'openclaw';
const normalizedArgv = normalizeArgv(process.argv);
if (needsRespawn()) {
respawnWithFlags(['--disable-warning=ExperimentalWarning']);
}
import('./cli').then(({ runCli }) => runCli(normalizedArgv));
export default defineConfig({
test: {
pool: 'forks',
testTimeout: 120_000,
maxWorkers: process.env.CI ? 3 : 16,
coverage: {
provider: 'v8',
thresholds: { lines: 70, functions: 70, statements: 70, branches: 55 }
}
}
});
package.jsonpnpm-workspace.yamltsconfig.jsontsdown.config.tsvitest.config.tssrc/entry.tssrc/index.tsopenclaw.mjs展示 Gateway 的分层架构,包括通信层(HTTP/WebSocket)、核心服务层和 Channel 集成层之间的关系
Complexity: 20 nodes, 25 edges
Related files:
src/gateway/server.impl.tssrc/gateway/server-http.tssrc/gateway/ws-connection.tssrc/channels/manager.ts展示 WebSocket 客户端从连接建立、握手、消息交换到断开的完整生命周期流程
Complexity: 18 nodes, 18 edges
Related files:
🔵 双协议架构设计 (info)
Gateway 同时支持 WebSocket 和 HTTP 协议,WebSocket 用于实时双向通信,HTTP 用于 API 调用和 Webhook 集成
🔵 插件化 Channel 抽象 (info)
通过 20+ 个适配器插件实现多平台支持,遵循统一的 ChannelPlugin 接口,便于扩展新平台
🔵 多层认证机制 (info)
支持 Token、Password、设备身份、Tailscale、Trusted Proxy 五种认证方式,覆盖不同部署场景
⚪ 单点瓶颈风险 (low)
Gateway 作为中心节点处理所有消息路由,高并发场景可能成为性能瓶颈,建议考虑分布式部署方案
⚪ 协议复杂度较高 (low)
90+ 个方法分布在 27 个模块中,维护和文档成本较高,建议加强 API 文档自动生成
async function startGatewayServer(config: GatewayConfig): Promise<GatewayServer> {
const resolvedConfig = await resolveConfig(config);
await loadPlugins(resolvedConfig.plugins);
const tlsOptions = await setupTLS(resolvedConfig.tls);
const runtime = createRuntime(resolvedConfig);
const services = {
nodeRegistry: createNodeRegistry(),
channelManager: createChannelManager(),
cron: createCronService(),
execApproval: createExecApprovalService(),
wizardSession: createWizardSession()
};
await startDiscovery(runtime);
attachGatewayWsConnectionHandler(server);
await startSidecars(runtime);
enableHotReload(runtime);
return { close: () => gracefulShutdown() };
}
class GatewayWsLifecycle {
onConnectionOpen(socket: WebSocket) {
const connId = crypto.randomUUID();
socket.send(JSON.stringify({
event: 'connect.challenge',
payload: { nonce: generateNonce() }
}));
startHandshakeTimeout(connId, 30000);
}
async onConnectRequest(socket: WebSocket, params: ConnectParams) {
const authResult = await authorizeGatewayConnect(params);
if (!authResult.ok) {
socket.close(1008, 'Policy Violation');
return;
}
const client = new GatewayWsClient(connId, authResult);
this.clients.add(client);
}
}
class GatewayClient {
private reconnectDelay = 1000;
private maxReconnectDelay = 30000;
async connect() {
try {
this.ws = new WebSocket(this.url);
const challenge = await this.waitForChallenge();
const response = await this.sendRequest('connect', connectParams);
this.startTickWatch(response.policy.tickInterval);
this.reconnectDelay = 1000;
} catch (error) {
await sleep(this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
return this.connect();
}
}
}
interface ChannelPlugin {
id: string;
meta: PluginMeta;
capabilities: string[];
config: ConfigSchema;
setup(): Promise<void>;
pairing(): PairingHandler;
security(): SecurityHandler;
messaging(): MessagingHandler;
groups?(): GroupHandler;
mentions?(): MentionHandler;
threading?(): ThreadingHandler;
streaming?(): StreamingHandler;
actions?(): ActionHandler[];
}
src/gateway/server.impl.tssrc/gateway/server-http.tssrc/gateway/ws-connection.tssrc/gateway/client.tssrc/gateway/protocol.tssrc/channels/manager.tsOpenClaw 的多渠道集成架构是一个精心设计的系统,它通过统一的适配器模式支持 8 个主流消息平台(WhatsApp、Telegram、Discord、Slack、Signal、iMessage、IRC 和 Google Chat),实现了真正的”一次编写,多端投递”的消息处理能力。这一架构的核心设计理念是将复杂的平台差异性封装在标准化接口之下,使得业务逻辑能够与具体的通信协议解耦。
多渠道集成采用经典的适配器模式(Adapter Pattern)作为核心设计范式。这一选择基于以下考量:每个消息平台都有独特的协议、API 规范和消息格式,直接集成会导致业务逻辑与平台细节深度耦合。通过适配器模式,系统为每个平台定义了统一的 ChannelPlugin 接口,将平台特定的协议处理封装在独立的适配器实现中。
插件的核心属性结构如下:
// 渠道插件核心接口定义
type ChannelPlugin = {
id: ChannelId; // 唯一标识符,如 'whatsapp', 'telegram'
meta: ChannelMeta; // 元数据:名称、描述、图标等
capabilities: ChannelCapabilities; // 平台能力声明
config: ChannelConfigAdapter; // 配置解析适配器
// 20个功能适配器
setup?: ChannelSetupAdapter; // 初始化流程
pairing?: ChannelPairingAdapter; // 设备配对(如 WhatsApp QR 码)
security?: ChannelSecurityAdapter; // 安全策略
messaging?: ChannelMessagingAdapter; // 消息收发
actions?: ChannelMessageActionAdapter; // 30+ 消息操作
// ... 其他适配器
};
这种设计使得添加新平台支持只需实现相应的适配器接口,而无需修改核心消息处理逻辑。目前系统定义了 20 种适配器类型,涵盖从初始化配对到消息操作的完整生命周期。
除了完整的插件实现,OpenClaw 引入了创新的 Dock 模式 来解决一个实际问题:某些代码路径(如配置验证、权限检查)只需要渠道的元数据和简单行为,而不需要加载完整的插件实现(这可能包含 Puppeteer、平台 SDK 等重量级依赖)。
// Dock:轻量级渠道元数据(无重量级依赖)
type ChannelDock = {
id: ChannelId;
capabilities: ChannelCapabilities;
commands?: ChannelCommandAdapter; // 命令配置
outbound?: { textChunkLimit?: number }; // 消息分片限制
streaming?: { // 流式输出配置
blockStreamingCoalesceDefaults?: { minChars: number; idleMs: number }
};
config?: {
resolveAllowFrom?: (params) => Array<string|number> | undefined;
formatAllowFrom?: (params) => string[];
};
groups?: ChannelGroupAdapter; // 群组行为
mentions?: ChannelMentionAdapter; // @提及处理
threading?: ChannelThreadingAdapter; // 线程构建
};
Dock 的核心规则是:只包含配置读取器、格式化函数、纯逻辑适配器,绝不包含监视器、探针、Web 登录或平台 SDK。这种分离使得共享代码路径能够快速访问渠道元数据,而不会因为加载重量级依赖而影响性能。
8 个核心平台各有独特的能力组合,系统通过 capabilities 声明来标准化这些差异:
| 平台 | 投票 | 线程 | 原生命令 | 反应 | 媒体 | 阻塞流式 | 消息分片 |
|---|---|---|---|---|---|---|---|
| Telegram | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ | 4000字符 |
| ✅ | ❌ | ❌ | ✅ | ✅ | ❌ | 4000字符 | |
| Discord | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | 2000字符 |
| Slack | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | 4000字符 |
| Signal | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | 4000字符 |
| iMessage | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | 4000字符 |
| IRC | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ | 350字符 |
| Google Chat | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | 4000字符 |
关键观察:
IRC 的特殊性:作为最古老的协议,IRC 的消息分片限制最小(350字符),且只支持基础的媒体功能和阻塞流式输出,不支持现代消息平台的反应、投票等特性。
Discord 的全面性:Discord 是能力最完整的平台,支持几乎所有特性,但消息长度限制为 2000 字符(最短的现代平台)。
企业平台的线程支持:Slack 和 Google Chat 作为企业通信工具,都完整支持线程功能;而 WhatsApp、Signal、iMessage 等个人通信应用则没有原生线程概念。
不同平台采用迥异的认证机制,系统通过 pairing 适配器统一抽象这一差异:
Telegram:使用 Bot API 模式,通过 @BotFather 获取 token,配置简单直接。
WhatsApp:采用 QR 码链接配对(基于 WhatsApp Web 协议),需要持久化会话状态以避免重复扫码。
Discord/Slack:使用 Bot Token 或 Socket Mode,支持 OAuth 流程。
Signal:通过 signal-cli 的 linked device REST API 连接,需要先在移动端完成设备关联。
// WhatsApp Dock 配置示例
whatsapp: {
id: 'whatsapp',
capabilities: {
chatTypes: ['direct', 'group'],
polls: true,
reactions: true,
media: true
},
commands: {
enforceOwnerForCommands: true, // 仅所有者可执行命令
skipWhenConfigEmpty: true // 无配置时跳过命令处理
},
outbound: { textChunkLimit: 4000 },
config: {
resolveAllowFrom: ({ cfg, accountId }) =>
resolveWhatsAppAccount({ cfg, accountId }).allowFrom ?? [],
formatAllowFrom: ({ allowFrom }) =>
allowFrom.map(e => normalizeWhatsAppTarget(String(e))).filter(Boolean),
},
mentions: {
stripPatterns: ({ ctx }) => {
const selfE164 = ctx.To?.replace(/^whatsapp:/, '');
return selfE164 ? [escapeRegExp(selfE164), `@${escaped}`] : [];
},
},
}
ChannelManager 是渠道运行时的核心控制器,通过 createChannelManager() 工厂函数创建。它负责管理所有渠道的启动、停止、状态监控和热重载。
// ChannelManager 核心 API
interface ChannelManager {
startChannels(): Promise<void>; // 批量启动所有配置的渠道
startChannel(id: ChannelId, accountId?: string): Promise<void>;
stopChannel(id: ChannelId, accountId?: string): Promise<void>;
getRuntimeSnapshot(id: ChannelId): ChannelRuntimeSnapshot;
markChannelLoggedOut(id: ChannelId, accountId?: string): void;
}
// 运行时状态快照
type ChannelRuntimeSnapshot = {
accountId: string;
running: boolean;
connected: boolean;
startedAt?: Date;
connectedAt?: Date;
lastError?: string;
mode: 'direct' | 'gateway' | 'hybrid';
dmPolicy: 'allowlist' | 'open' | 'disabled';
allowFrom: string[];
};
渠道启动是一个精心编排的多步骤过程:
resolveChannelConfig() 解析渠道配置,合并默认值enabled && configured)gateway.startAccount() 启动账户连接tasks 集合以便监控停止流程则是逆向操作:触发 abort 信号 → 调用 gateway.stopAccount() → 清理运行时状态。
每个渠道支持独立的多账户管理,这对于企业场景尤为重要(如多个 WhatsApp 商务号、多个 Discord 机器人)。账户之间具有独立的生命周期,通过 accountId 区分:
// 多账户启动示例
await channelManager.startChannel('whatsapp', 'sales-team');
await channelManager.startChannel('whatsapp', 'support-team');
// 默认账户解析
const defaultId = resolveChannelDefaultAccountId(cfg, 'whatsapp');
热重载通过 reload.configPrefixes 和 noopPrefixes 配置实现,允许在不重启服务的情况下更新渠道配置。
系统定义了超过 30 种消息操作,通过 ChannelMessageActionAdapter 接口统一暴露给上层调用:
send、broadcast、reply、edit、unsend/delete、sendAttachmentreact、reactions(列出反应)、poll(创建投票)、read(标记已读)thread-create、thread-list、thread-replypin/unpin、renameGroup、setGroupIcon、addParticipant/removeParticipantDiscord 作为能力最丰富的平台,拥有大量专属操作:
channel-info/list/create/edit/delete/movecategory-create/edit/deleterole-info/add/removetimeout、kick、banemoji-list/upload、sticker/sticker-search/uploadvoice-statusevent-list/create// 操作适配器接口
type ChannelMessageActionAdapter = {
listActions?: (cfg) => ChannelMessageActionName[]; // 列出支持的操作
supportsAction?: (action) => boolean; // 检查是否支持
supportsButtons?: (cfg) => boolean; // 交互式按钮支持
supportsCards?: (cfg) => boolean; // 富文本卡片支持
extractToolSend?: (args) => ChannelToolSend | null; // 提取发送目标
handleAction?: (ctx: ChannelMessageActionContext) => Promise<AgentToolResult>;
};
// 操作执行上下文
type ChannelMessageActionContext = {
channel: ChannelId; // 目标渠道
action: ChannelMessageActionName; // 操作名称
cfg: OpenClawConfig; // 配置对象
params: Record<string, unknown>; // 操作参数
accountId?: string; // 账户 ID
gateway?: { // 网关连接信息
url: string;
token: string;
timeoutMs: number;
clientName: string;
mode: 'direct' | 'gateway';
};
toolContext?: ChannelThreadingToolContext; // 线程上下文
dryRun?: boolean; // 验证模式(不实际执行)
};
渠道插件通过 OpenClawPluginApi 进行注册,支持两种方式:
// 方式 1:完整注册(插件 + Dock)
api.registerChannel({
plugin: myChannelPlugin, // 完整的 ChannelPlugin 实现
dock: myChannelDock, // 可选的轻量级 Dock
});
// 方式 2:简化注册(仅插件)
api.registerChannel(myChannelPlugin);
type PluginChannelRegistration = {
pluginId: string; // 所属插件的 ID
plugin: ChannelPlugin; // 完整渠道实现
dock?: ChannelDock; // 可选轻量级 Dock
source: string; // 注册来源文件路径
};
import { listChannelPlugins, getChannelPlugin, normalizeChannelId }
from 'src/channels/plugins/index.ts';
// 列出所有注册的渠道(按优先级排序)
const plugins = listChannelPlugins();
// 获取特定渠道插件
const whatsapp = getChannelPlugin('whatsapp');
// ID 规范化(处理别名)
normalizeChannelId('imsg'); // 返回: 'imessage'
normalizeChannelId('gchat'); // 返回: 'googlechat'
// 核心渠道优先级顺序
const CHAT_CHANNEL_ORDER = [
'telegram', 'whatsapp', 'discord', 'irc',
'googlechat', 'slack', 'signal', 'imessage'
];
// 常用别名映射
const CHAT_CHANNEL_ALIASES = {
'imsg': 'imessage',
'internet-relay-chat': 'irc',
'google-chat': 'googlechat',
'gchat': 'googlechat',
};
系统支持三种消息投递模式,适应不同的部署场景:
每个渠道可以独立配置投递模式,网关模式提供了额外的好处:集中式日志、统一认证、负载均衡和故障转移。
对于支持线程的平台(Telegram、Discord、Slack、Google Chat),系统通过 threading 适配器构建上下文:
// 线程上下文结构
type ChannelThreadingToolContext = {
currentChannelId: string; // 当前会话 ID
currentThreadTs?: string; // 线程时间戳/消息 ID
hasRepliedRef: boolean; // 是否已回复
replyToMode: 'off' | 'first' | 'all'; // 回复模式
skipCrossContextDecoration: boolean; // 跳过跨上下文标记
};
多渠道架构的安全关键点:
dmPolicy(allowlist、open、disabled)allowFrom 限制可交互的用户/群组enforceOwnerForCommands 确保敏感命令仅所有者可用commands.useAccessGroups 实现细粒度权限控制🔵 采用适配器模式实现多渠道抽象 (info)
通过 20 种适配器类型和 Dock 轻量级元数据模式,实现了 8 个消息平台的统一集成
🔵 完善的多账户生命周期管理 (info)
ChannelManager 支持独立的多账户生命周期,适合企业多账号场景
⚪ 适配器接口复杂度较高 (low)
20 种适配器类型增加了实现新渠道的门槛,建议提供更多模板和文档
🔵 细粒度的渠道安全策略 (info)
每个渠道独立配置 DM 策略、允许列表和命令权限
type ChannelPlugin = {
id: ChannelId;
meta: ChannelMeta;
capabilities: ChannelCapabilities;
config: ChannelConfigAdapter;
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
messaging?: ChannelMessagingAdapter;
actions?: ChannelMessageActionAdapter;
};
type ChannelDock = {
id: ChannelId;
capabilities: ChannelCapabilities;
commands?: ChannelCommandAdapter;
outbound?: { textChunkLimit?: number };
config?: {
resolveAllowFrom?: (params) => Array<string|number> | undefined;
};
};
whatsapp: {
id: 'whatsapp',
capabilities: { chatTypes: ['direct', 'group'], polls: true, reactions: true, media: true },
commands: { enforceOwnerForCommands: true, skipWhenConfigEmpty: true },
outbound: { textChunkLimit: 4000 },
}
type ChannelMessageActionContext = {
channel: ChannelId;
action: ChannelMessageActionName;
params: Record<string, unknown>;
gateway?: { url: string; token: string; mode: 'direct' | 'gateway' };
dryRun?: boolean;
};
src/channels/plugins/types.plugin.tssrc/channels/dock.tssrc/channels/registry.tssrc/channels/manager.tssrc/channels/plugins/index.tsOpenClaw 的插件系统是整个平台可扩展性的核心支柱,它允许开发者在不修改核心代码的情况下扩展系统功能。该系统采用了现代化的依赖注入模式,提供了丰富的 API 表面积,支持从简单的工具注册到复杂的通道适配器等多种扩展类型。本章节将深入分析插件系统的架构设计、生命周期管理、API 注册机制以及运行时依赖注入模式。
插件系统由三个核心组件构成:PluginRegistry(插件注册表)、PluginLoader(插件加载器)和 PluginRuntime(插件运行时)。这种分离设计遵循了单一职责原则,使得每个组件都专注于特定的功能领域。
插件类型丰富性:系统支持 10+ 种扩展类型,覆盖了平台的各个层面:
| 扩展类型 | 注册方法 | 用途说明 |
|---|---|---|
| Tools(工具) | registerTool() |
为 Agent 提供可调用的工具能力 |
| Hooks(钩子) | registerHook(), api.on() |
生命周期事件拦截与处理 |
| Channels(通道) | registerChannel() |
平台通信适配器(如 Discord, Telegram) |
| Providers(提供者) | registerProvider() |
认证与服务提供者 |
| Gateway Methods | registerGatewayMethod() |
RPC 方法扩展 |
| HTTP Handlers | registerHttpHandler/Route() |
HTTP 端点扩展 |
| CLI Commands | registerCli() |
命令行工具扩展 |
| Services(服务) | registerService() |
后台服务注册 |
| Commands(命令) | registerCommand() |
聊天命令扩展 |
插件定义结构:每个插件通过标准化的接口定义,包含 id、name、description、version、kind(如 'memory')、configSchema(JSON Schema 配置验证)以及关键的 register(api) 或 activate(api) 入口函数。
插件系统采用多源发现机制,按优先级顺序从四个来源加载插件:
plugins.loadPaths 配置项显式指定的路径.openclaw/extensions/ 目录~/.openclaw/extensions/ 目录这种分层设计允许项目级配置覆盖全局配置,而显式配置又能覆盖自动发现的插件,提供了极大的灵活性。
候选检测机制:系统扫描 .ts/.js/.mts/.cts/.mjs/.cjs 文件,读取 package.json 中的 openclaw.extensions 清单,支持目录下的 index.ts/js 入口文件,并从包名或文件名推导插件 ID。
完整的加载流程如下:
// 插件加载流程(简化示意)
async function loadOpenClawPlugins(config: OpenClawConfig) {
// 1. 缓存检查 - 配置未变则返回缓存的 registry
const cacheKey = `${workspaceDir}::${JSON.stringify(normalizedPluginsConfig)}`;
if (registryCache.has(cacheKey)) return registryCache.get(cacheKey);
// 2. 创建运行时 - 提供 API 访问能力
const runtime = createPluginRuntime();
// 3. 发现阶段 - 按优先级扫描所有候选插件
const candidates = await discoverOpenClawPlugins(config);
// 4. 逐个加载插件
for (const candidate of candidates) {
// 4.1 加载清单并验证
const manifest = await loadPluginManifest(candidate);
// 4.2 检查是否启用
if (!resolvePluginEnabled(manifest, config)) continue;
// 4.3 配置验证 (JSON Schema)
validatePluginConfig(manifest.configSchema, config.plugins[manifest.id]);
// 4.4 使用 jiti 加载模块(支持 TypeScript 透明编译)
const pluginModule = await jiti.import(candidate.source);
// 4.5 创建插件专属 API
const api = createApi(manifest, runtime, registry);
// 4.6 执行注册/激活
await (pluginModule.register?.(api) || pluginModule.activate?.(api));
}
// 5. 全局激活
setActivePluginRegistry(registry);
initializeGlobalHookRunner();
return registry;
}
运行时状态管理:系统使用 Symbol.for('openclaw.pluginRegistryState') 作为全局单例的键,通过 getActivePluginRegistry() 和 requireActivePluginRegistry() 提供安全的访问接口。
插件 API 是插件与核心系统交互的唯一接口,设计上遵循”最小必要权限”原则:
// 插件 API 结构
interface PluginApi {
// 身份标识
readonly id: string;
readonly name: string;
readonly version?: string;
readonly source: string;
// 配置访问
readonly config: OpenClawConfig;
readonly pluginConfig?: unknown; // 插件专属配置
// 运行时注入
readonly runtime: PluginRuntime;
readonly logger: PluginLogger;
// 注册方法
registerTool(tool: Tool, opts?: ToolOpts): void;
registerHook(events: string[], handler: HookHandler, opts?: HookOpts): void;
registerHttpHandler(handler: HttpHandler): void;
registerHttpRoute(params: HttpRouteParams): void;
registerChannel(registration: ChannelRegistration): void;
registerProvider(provider: Provider): void;
registerGatewayMethod(method: string, handler: GatewayHandler): void;
registerCli(registrar: CliRegistrar, opts?: CliOpts): void;
registerService(service: Service): void;
registerCommand(command: Command): void;
on<K extends HookName>(hookName: K, handler: TypedHookHandler<K>, opts?: HookOpts): void;
// 工具方法
resolvePath(input: string): string; // 相对路径解析
}
每个插件实例获得一个隔离的 API 对象,确保插件之间互不干扰。这种设计使得插件的行为可预测、可追踪。
钩子系统是插件能力扩展的关键机制,提供了 15 个生命周期钩子,覆盖 Agent 执行、消息处理、工具调用、会话管理和网关操作等核心流程:
| 类别 | 钩子名称 | 执行模式 | 说明 |
|---|---|---|---|
| Agent | before_agent_start |
顺序 | Agent 启动前,可修改 systemPrompt |
| Agent | agent_end |
并行 | Agent 执行结束 |
| Agent | before_compaction |
并行 | 上下文压缩前 |
| Agent | after_compaction |
并行 | 上下文压缩后 |
| Agent | before_reset |
并行 | 状态重置前 |
| Message | msg_sending |
顺序 | 消息发送前,可修改内容 |
| Message | msg_received |
并行 | 消息接收后 |
| Message | msg_sent |
并行 | 消息发送后 |
| Tool | before_tool_call |
顺序 | 工具调用前,可拦截或修改参数 |
| Tool | tool_result_persist |
顺序 | 工具结果持久化 |
| Tool | after_tool_call |
并行 | 工具调用后 |
| Session | sess_start |
并行 | 会话开始 |
| Session | sess_end |
并行 | 会话结束 |
| Gateway | gateway_start |
并行 | 网关启动 |
| Gateway | gateway_stop |
并行 | 网关停止 |
执行模式:顺序执行(Sequential)的钩子按优先级从高到低执行,可以修改数据或中断流程;并行执行(Parallel)的钩子同时触发,适用于日志、监控等不影响主流程的操作。
顺序执行钩子的结果可以返回 { systemPrompt?: string, prependContext?: string } 来修改 Agent 行为。
PluginRuntime 是依赖注入的核心,它提供了对 OpenClaw 核心功能的受控访问,避免了插件直接 import 内部模块的紧耦合问题:
type PluginRuntime = {
version: string;
config: {
loadConfig: () => OpenClawConfig;
writeConfigFile: (config: Partial<OpenClawConfig>) => void;
};
system: {
enqueueSystemEvent: (event: SystemEvent) => void;
runCommandWithTimeout: (cmd: string, timeout: number) => Promise<string>;
formatNativeDependencyHint: (dep: string) => string;
};
media: {
loadWebMedia: (url: string) => Promise<MediaBuffer>;
detectMime: (buffer: Buffer) => string;
mediaKindFromMime: (mime: string) => MediaKind;
isVoiceCompatibleAudio: (mime: string) => boolean;
getImageMetadata: (buffer: Buffer) => ImageMetadata;
resizeToJpeg: (buffer: Buffer, opts: ResizeOpts) => Promise<Buffer>;
};
tts: {
textToSpeechTelephony: (text: string, voice: string) => Promise<Buffer>;
};
tools: {
createMemoryGetTool: () => Tool;
createMemorySearchTool: () => Tool;
registerMemoryCli: (cli: Commander) => void;
};
channel: {
text: { chunkByNewline, chunkMarkdownText, resolveChunkMode, ... },
reply: { dispatchReplyFromConfig, finalizeInboundContext, ... },
routing: { resolveAgentRoute },
pairing: { buildPairingReply, readAllowFromStore, ... },
// 平台专属 API
discord: { probeDiscord, sendMessageDiscord, monitorDiscordProvider, ... },
slack: { probeSlack, sendMessageSlack, monitorSlackProvider, ... },
telegram: { probeTelegram, sendMessageTelegram, ... },
// ... 更多平台
};
logging: {
shouldLogVerbose: () => boolean;
getChildLogger: (name: string) => PluginLogger;
};
state: {
resolveStateDir: () => string;
};
};
设计优势:
runtime.channel.[platform] 下独立组织runtime.version 提供向后兼容性检查服务上下文为长期运行的服务提供额外的生命周期支持:
type OpenClawPluginServiceContext = {
config: OpenClawConfig;
workspaceDir?: string;
stateDir: string; // 持久化状态目录
logger: PluginLogger;
};
PluginRegistry 是所有已注册扩展的中央存储,结构如下:
type PluginRegistry = {
plugins: PluginRecord[]; // 所有插件元数据
tools: PluginToolRegistration[]; // Agent 工具
hooks: PluginHookRegistration[]; // 旧版事件钩子
typedHooks: TypedPluginHookRegistration[]; // 类型化生命周期钩子
channels: PluginChannelRegistration[]; // 通道适配器
providers: PluginProviderRegistration[]; // 认证提供者
gatewayHandlers: GatewayRequestHandlers; // RPC 方法
httpHandlers: PluginHttpRegistration[]; // HTTP 处理器
httpRoutes: PluginHttpRouteRegistration[]; // 命名 HTTP 路由
cliRegistrars: PluginCliRegistration[]; // CLI 扩展
services: PluginServiceRegistration[]; // 后台服务
commands: PluginCommandRegistration[]; // 聊天命令
diagnostics: PluginDiagnostic[]; // 错误/警告信息
};
验证机制防止冲突和错误配置:
| 冲突类型 | 诊断级别 | 说明 |
|---|---|---|
| 重复的 Gateway 方法 | Error | 同名 RPC 方法只能注册一次 |
| 重复的 HTTP 路由 | Error | 同路径/方法组合必须唯一 |
| 重复的命令 | Error | 聊天命令名称冲突 |
| 缺失钩子名称 | Warning | 钩子注册但未指定事件名 |
| 缺失插件 ID | Warning | 插件未提供有效标识 |
诊断系统为插件开发者提供调试信息:
type PluginDiagnostic = {
level: 'warn' | 'error';
message: string;
pluginId?: string;
source?: string;
};
优势:
潜在改进:
开发建议:
configSchema 定义插件配置,支持自动验证和 UI 生成api.on<K>() 类型化钩子而非通用 registerHook()registerService() 中实现清理逻辑,确保资源释放runtime.logging.getChildLogger() 创建命名空间日志🔵 模块化插件系统设计 (info)
系统提供 10+ 扩展类型,覆盖工具、钩子、通道、HTTP、CLI 等所有核心功能,遵循开闭原则
🔵 依赖注入模式 (info)
通过 PluginRuntime 注入核心能力,避免直接 import 内部模块,提高可测试性和 API 稳定性
🔵 多源插件发现机制 (info)
支持 config、workspace、global、bundled 四层优先级,提供灵活的覆盖策略
⚪ 缺少插件间依赖管理 (low)
当前系统不支持声明插件依赖关系和加载顺序控制,可能导致运行时错误
⚪ 插件沙箱隔离不足 (low)
插件共享 Node.js 进程,恶意或有缺陷的插件可能影响系统稳定性
async function loadOpenClawPlugins(config: OpenClawConfig) {
const cacheKey = `${workspaceDir}::${JSON.stringify(normalizedPluginsConfig)}`;
if (registryCache.has(cacheKey)) return registryCache.get(cacheKey);
const runtime = createPluginRuntime();
const candidates = await discoverOpenClawPlugins(config);
for (const candidate of candidates) {
const manifest = await loadPluginManifest(candidate);
if (!resolvePluginEnabled(manifest, config)) continue;
validatePluginConfig(manifest.configSchema, config.plugins[manifest.id]);
const pluginModule = await jiti.import(candidate.source);
const api = createApi(manifest, runtime, registry);
await (pluginModule.register?.(api) || pluginModule.activate?.(api));
}
setActivePluginRegistry(registry);
initializeGlobalHookRunner();
return registry;
}
type PluginRuntime = {
version: string;
config: { loadConfig, writeConfigFile };
system: { enqueueSystemEvent, runCommandWithTimeout, formatNativeDependencyHint };
media: { loadWebMedia, detectMime, mediaKindFromMime, getImageMetadata, resizeToJpeg };
tts: { textToSpeechTelephony };
tools: { createMemoryGetTool, createMemorySearchTool, registerMemoryCli };
channel: {
text: { chunkByNewline, chunkMarkdownText, ... },
discord: { probeDiscord, sendMessageDiscord, ... },
slack: { probeSlack, sendMessageSlack, ... },
telegram: { probeTelegram, sendMessageTelegram, ... }
};
logging: { shouldLogVerbose, getChildLogger };
state: { resolveStateDir };
};
type PluginRegistry = {
plugins: PluginRecord[]; // 所有插件元数据
tools: PluginToolRegistration[]; // Agent 工具
hooks: PluginHookRegistration[]; // 旧版事件钩子
typedHooks: TypedPluginHookRegistration[]; // 类型化生命周期钩子
channels: PluginChannelRegistration[]; // 通道适配器
providers: PluginProviderRegistration[]; // 认证提供者
gatewayHandlers: GatewayRequestHandlers; // RPC 方法
httpHandlers: PluginHttpRegistration[]; // HTTP 处理器
httpRoutes: PluginHttpRouteRegistration[]; // 命名 HTTP 路由
cliRegistrars: PluginCliRegistration[]; // CLI 扩展
services: PluginServiceRegistration[]; // 后台服务
commands: PluginCommandRegistration[]; // 聊天命令
diagnostics: PluginDiagnostic[]; // 错误/警告信息
};
src/plugins/registry.tssrc/plugins/loader.tssrc/plugins/runtime.tssrc/plugins/api.tssrc/hooks/hook-runner.tsOpenClaw 实现了一个复杂而精密的多智能体系统架构,支持智能体编排、层级化会话管理、跨智能体通信以及自动故障恢复。该系统的设计理念是将复杂任务分解给多个专业化智能体协作完成,同时确保执行的可靠性和可追溯性。本节将深入剖析这一架构的核心组件、通信模式和状态管理机制。
智能体运行引擎是整个多智能体系统的核心,负责管理智能体的生命周期、执行流程和错误恢复。该引擎由三个关键函数组成,总计超过 2000 行代码,体现了系统对可靠性和可扩展性的高度重视。
runReplyAgent 函数(523 LOC)是智能体执行的主入口点,负责协调整个执行流程:
// 简化的 runReplyAgent 执行流程
async function runReplyAgent(params: AgentRunParams): Promise<AgentRunResult> {
// 1. 会话管理初始化
const sessionEntry = await getOrCreateSession(params.sessionKey);
const sessionStore = createSessionStore(sessionEntry);
// 2. 打字状态控制(模拟人类交互)
const typingController = new TypingController(params.channel);
await typingController.startTyping();
try {
// 3. 内存刷新(确保持久化)
await runMemoryFlushIfNeeded(sessionStore);
// 4. 带回退的智能体回合执行
const result = await runAgentTurnWithFallback({
sessionStore,
provider: params.provider,
model: params.model,
...params
});
// 5. 创建流式响应管道
const pipeline = createBlockReplyPipeline(result);
// 6. 创建后续执行器(处理队列中的待处理任务)
const followupRunner = createFollowupRunner(params.sessionKey);
await followupRunner.run();
// 7. 使用量追踪
await trackUsage(result.usage);
return result;
} finally {
await typingController.stopTyping();
}
}
这个函数体现了几个重要的设计决策:首先,会话管理采用懒加载模式(getOrCreateSession),只在需要时创建会话;其次,打字控制器用于模拟人类交互体验,避免用户感觉在与机器对话;最后,后续执行器确保未完成的任务能够被妥善处理。
runAgentTurnWithFallback 函数(573 LOC)实现了智能体执行的弹性层,处理各种故障场景:
async function runAgentTurnWithFallback(params: TurnParams): Promise<TurnResult> {
// 每次执行生成唯一 runId 用于追踪
const runId = generateUUID();
// 带模型回退的执行
const result = await runWithModelFallback({
primary: params.model,
fallbacks: params.fallbacksOverride ?? config.agents.defaults.model.fallbacks,
run: async (provider, model) => {
return await executeAgentTurn({ ...params, provider, model });
}
});
// 自动压缩:上下文过长时触发
if (result.contextOverflow) {
await triggerAutoCompaction(params.sessionKey);
}
// 会话重置:检测到损坏或角色错误时
if (isSessionCorrupted(result.error) || isRoleError(result.error)) {
await resetSession(params.sessionKey);
return await runAgentTurnWithFallback(params); // 重试
}
// HTTP 重试:网络错误时等待 2.5 秒后重试
if (isHttpError(result.error)) {
await delay(2500);
return await runAgentTurnWithFallback(params);
}
// 工具截断:过长的工具输出被截断
if (result.toolOutputTruncated) {
logger.warn('Tool output truncated due to length');
}
return result;
}
该函数的弹性设计体现在多个层面:模型回退机制确保即使主模型不可用,系统也能自动切换到备用模型;自动压缩机制处理上下文过长问题;会话重置机制应对数据损坏;HTTP 重试机制处理临时网络故障。这种多层防护使系统具有极高的可用性。
为了管理多智能体的并发执行,OpenClaw 实现了一个精巧的命令队列和车道(Lane)系统。这个系统确保不同类型的任务能够有序执行,同时避免资源竞争。
系统定义了四种车道类型,每种服务于不同的执行场景:
| 车道类型 | 用途 | 默认并发 | 典型场景 |
|---|---|---|---|
| Main | 默认车道 | 1 | 用户交互请求 |
| Cron | 定时任务 | 1 | 计划任务执行 |
| Subagent | 子智能体 | 1 | 子任务委托 |
| Nested | 嵌套调用 | 1 | 智能体间同步调用 |
车道的核心数据结构如下:
type LaneState = {
lane: string; // 车道标识
queue: Task[]; // 待执行任务队列
activeTaskIds: Set<string>; // 正在执行的任务
maxConcurrent: number; // 最大并发数(默认 1)
draining: boolean; // 是否正在排空
generation: number; // 代数追踪(防止过时任务)
};
// 关键 API
function enqueueCommandInLane(lane: string, task: Task): void;
function setCommandLaneConcurrency(lane: string, n: number): void;
function clearCommandLane(lane: string): void;
function resetAllLanes(): void;
function waitForActiveTasks(lane: string): Promise<void>;
每个会话都有独立的车道,确保同一会话的请求按顺序执行:
// 会话车道:session:agent:main:topic
function resolveSessionLane(sessionKey: string): string {
return `session:${sessionKey}`;
}
// 全局车道:用于共享任务
function resolveGlobalLane(lane?: CmdLane): string {
return lane ?? CmdLane.Main;
}
执行流程如下:
CommandLaneClearedError子智能体系统(1500+ LOC)允许智能体动态创建和管理子智能体,形成层级化的任务执行结构。这是处理复杂任务的关键机制。
子智能体使用层级化的会话键进行标识和管理:
// 基础格式:agent:{parentId}:subagent:{label}
'agent:main:subagent:researcher'
// 嵌套格式:多层子智能体
'agent:main:subagent:analyst:subagent:data_collector'
// 实用函数
function isSubagentSessionKey(key: string): boolean;
function getSubagentDepth(key: string): number; // 返回嵌套深度
function isCronSessionKey(key: string): boolean;
function isAcpSessionKey(key: string): boolean;
注册表(744 LOC)负责追踪所有活跃的子智能体:
type RunRecord = {
runId: string; // 唯一执行标识
sessionKey: string; // 子智能体会话键
parentSessionKey: string; // 父智能体会话键
task: string; // 任务描述
cleanup: () => void; // 清理函数
startedAt: number; // 开始时间
endedAt?: number; // 结束时间
outcome?: 'success' | 'error' | 'killed'; // 执行结果
};
// 注册表功能
- 持久化:子智能体状态持久存储
- 清扫器:定期清理过期记录
- 通知流程:子智能体完成时通知父智能体
- 重启转向:允许重定向到新实例
为防止无限递归,系统通过 maxSpawnDepth 限制嵌套层级:
// 配置示例
const agentConfig = {
subagents: {
allowAgents: ['researcher', 'coder', 'reviewer'], // 允许生成的子智能体类型
maxSpawnDepth: 2 // 最大嵌套深度:0=禁止,1=单层,2=两层
}
};
// 深度 0:主智能体(main)
// 深度 1:main -> researcher
// 深度 2:main -> researcher -> data_collector
// 深度 3+:被阻止
智能体事件系统提供了统一的方式来追踪和监控智能体执行过程中的各种状态变化。
type AgentEventPayload = {
runId: string; // 执行标识
seq: number; // 单调递增序列号
stream: AgentEventStream; // 事件流类型
ts: number; // 时间戳
data: Record<string, unknown>; // 事件数据
sessionKey?: string; // 关联会话
};
// 事件流类型
type AgentEventStream =
| 'lifecycle' // 生命周期事件(启动/结束)
| 'tool' // 工具执行事件
| 'assistant' // 助手文本输出
| 'error' // 错误事件
| 'compaction'; // 上下文压缩事件
// 发射事件
emitAgentEvent({
runId: 'uuid-xxx',
stream: 'lifecycle',
data: { phase: 'start', startedAt: Date.now() }
});
// 自动递增 seq,自动从上下文填充 sessionKey
// 注册监听器
const unsubscribe = onAgentEvent((evt: AgentEventPayload) => {
console.log(`[${evt.stream}] ${evt.data.phase}`);
});
// 返回取消订阅函数
// 运行上下文管理
registerAgentRunContext(runId, { sessionKey, verboseLevel });
getAgentRunContext(runId); // 检索
clearAgentRunContext(runId); // 清理
事件系统在网关中有重要应用:server-chat.ts 缓冲事件用于 WebSocket 推送,server.impl.ts 在运行完成时清理上下文,工具执行发射 start/update/end 三阶段事件,压缩过程发射 start/end 及 token 计数。
后续执行队列(Followup Queue)管理智能体响应后需要执行的额外任务,支持多种队列模式以适应不同场景。
| 模式 | 描述 | 使用场景 |
|---|---|---|
| steer | 重定向消息到现有运行 | 实时干预 |
| followup | 当前运行完成后执行 | 延续任务 |
| collect | 聚合消息批量处理 | 批处理场景 |
| steer-backlog | 带积压支持的转向 | 复杂重定向 |
| interrupt | 优先级中断 | 紧急任务 |
| queue | 标准 FIFO 队列 | 常规排队 |
type FollowupRun = {
prompt: string; // 要执行的提示
messageId?: string; // 去重标识
summaryLine?: string; // 摘要描述
enqueuedAt: number; // 入队时间
originatingChannel?: string; // 来源渠道
originatingTo?: string; // 目标地址
run: {
agentId: string;
sessionKey: string;
provider: string;
model: string;
timeoutMs: number;
// ...其他执行参数
};
};
type QueueSettings = {
mode: QueueMode;
debounceMs?: number; // 防抖延迟
cap?: number; // 最大队列大小
dropPolicy?: 'old' | 'new' | 'summarize'; // 溢出策略
};
后续运行器(289 LOC)负责:顺序执行队列中的提示、将回复路由到来源渠道、处理模型回退、追踪使用量和压缩、过滤重复的消息工具调用。
智能体之间的通信是多智能体协作的关键。OpenClaw 实现了多种通信模式以支持不同的协作场景。
runAgentStep 函数(81 LOC)允许一个智能体同步调用另一个智能体:
async function runAgentStep(params: {
sessionKey: string; // 目标会话
message: string; // 发送的消息
extraSystemPrompt: string; // 附加系统提示
timeoutMs: number; // 超时时间
channel?: string; // 默认: INTERNAL_MESSAGE_CHANNEL
lane?: string; // 默认: AGENT_LANE_NESTED
sourceSessionKey?: string; // 调用者会话(来源追踪)
sourceTool?: string; // 默认: 'sessions_send'
}): Promise<string | undefined> {
// 1. 生成幂等键
const idempotencyKey = generateUUID();
// 2. 调用网关 agent 方法
await callGateway({
method: 'agent',
params: {
sessionKey: params.sessionKey,
message: params.message,
deliver: false, // 不发送到渠道,仅执行
lane: AGENT_LANE_NESTED, // 使用嵌套车道
inputProvenance: {
kind: 'inter_session',
sourceSessionKey: params.sourceSessionKey,
sourceChannel: params.channel,
sourceTool: params.sourceTool
}
}
});
// 3. 等待完成
await callGateway({ method: 'agent.wait', params: { sessionKey } });
// 4. 读取最新助手回复
return await readLatestAssistantReply(params.sessionKey);
}
每次智能体间调用都记录详细的来源信息,用于调试和审计:
type InputProvenance = {
kind: 'inter_session'; // 来源类型
sourceSessionKey: string; // 源会话
sourceChannel: string; // 源渠道
sourceTool: string; // 触发工具
};
| 模式 | 机制 | 车道 | 典型用途 |
|---|---|---|---|
| 直接网关调用 | callGateway({ method: 'agent' }) |
目标会话车道 | 异步消息发送 |
| 会话工具 | sessions_send |
Nested | 智能体间消息 |
| 子智能体生成 | sessions_spawn |
Subagent | 创建子任务处理者 |
| 通知流程 | Announce | 父智能体车道 | 子完成通知 |
模型回退系统(394 LOC)确保即使主模型不可用,系统也能继续工作。
function resolveFallbackCandidates(params: {
cfg: OpenClawConfig;
provider: string;
model: string;
fallbacksOverride?: string[]; // 每智能体覆盖
}): ModelCandidate[] {
// 优先级:
// 1. 请求中指定的主模型
// 2. 配置中的全局回退列表
// 3. 默认配置的主模型
}
run(provider, model) 回调class FailoverError extends Error {
reason: 'rate_limit' | 'auth' | 'unknown';
}
// 不触发故障转移的情况
- AbortError: 用户取消(直接重抛)
- 上下文溢出: 会话问题,非模型问题(直接重抛)
智能体配置系统(209 LOC)管理每个智能体的能力、限制和行为。
type AgentEntry = {
id: string; // 智能体标识符
name?: string; // 显示名称
default?: boolean; // 是否为默认智能体
workspace?: string; // 工作空间目录
agentDir?: string; // 智能体状态目录
model?: string | { // 模型配置
primary?: string;
fallbacks?: string[];
};
skills?: string[]; // 启用的技能
subagents?: { // 子智能体生成规则
allowAgents?: string[]; // '*' 或智能体 ID 列表
maxSpawnDepth?: number; // 嵌套限制
};
tools?: ToolConfig; // 工具配置
// ...其他配置
};
listAgentIds(cfg): 列出所有配置的智能体 IDresolveDefaultAgentId(cfg): 获取主智能体(第一个 default:true 或第一条目)resolveSessionAgentId({ sessionKey, config }): 从会话键解析智能体resolveAgentConfig(cfg, agentId): 获取完整配置resolveAgentWorkspaceDir(cfg, agentId): 解析工作空间路径| 文件 | 行数 | 职责 |
|---|---|---|
agents/agent-runner.ts |
523 | 主执行引擎 |
agents/agent-turn.ts |
573 | 回合执行与回退 |
agents/embedded-runner.ts |
998 | 嵌入式运行器 |
queue/lanes.ts |
287 | 车道管理 |
agents/subagent-registry.ts |
744 | 子智能体注册 |
agents/subagent-tools.ts |
756 | 子智能体工具 |
infra/agent-events.ts |
84 | 事件系统 |
queue/followup-runner.ts |
289 | 后续执行器 |
agents/model-fallback.ts |
394 | 模型回退 |
agents/agent-scope.ts |
209 | 配置解析 |
🔵 层级化多智能体架构 (info)
系统实现了完整的智能体层级结构,支持父子关系追踪、深度控制和生命周期管理
🔵 车道隔离机制 (info)
通过 4 种车道类型(Main、Cron、Subagent、Nested)实现执行隔离,防止不同类型任务相互干扰
🔵 多层弹性设计 (info)
模型回退、认证轮换、会话重置、HTTP 重试等机制确保系统高可用
⚪ 默认单线程执行 (low)
队列默认并发为 1,高负载场景可能需要调整 maxConcurrent 参数
async function runReplyAgent(params) {
const sessionEntry = await getOrCreateSession(params.sessionKey);
const typingController = new TypingController(params.channel);
await typingController.startTyping();
try {
await runMemoryFlushIfNeeded(sessionStore);
const result = await runAgentTurnWithFallback({ ... });
const followupRunner = createFollowupRunner(params.sessionKey);
await followupRunner.run();
return result;
} finally {
await typingController.stopTyping();
}
}
type LaneState = {
lane: string;
queue: Task[];
activeTaskIds: Set<string>;
maxConcurrent: number; // 默认 1
draining: boolean;
generation: number; // 防止过时任务
};
async function runAgentStep(params) {
const idempotencyKey = generateUUID();
await callGateway({
method: 'agent',
params: {
sessionKey: params.sessionKey,
deliver: false,
lane: AGENT_LANE_NESTED,
inputProvenance: {
kind: 'inter_session',
sourceSessionKey: params.sourceSessionKey
}
}
});
await callGateway({ method: 'agent.wait', ... });
return await readLatestAssistantReply(params.sessionKey);
}
src/agents/agent-runner.tssrc/agents/agent-turn.tssrc/queue/lanes.tssrc/agents/subagent-registry.tssrc/infra/agent-events.tssrc/queue/followup-runner.tssrc/agents/model-fallback.tssrc/agents/agent-scope.tssrc/tools/agent-step.ts展示从麦克风输入到 Hook 命令执行的完整数据流,包括音频采集、格式转换、语音转录、唤醒词检测和命令执行各阶段。
Complexity: 13 nodes, 14 edges
Related files:
packages/swabble/Sources/SwabbleCore/SpeechPipeline.swiftpackages/swabble/Sources/SwabbleCore/BufferConverter.swiftpackages/swabble/Sources/SwabbleKit/WakeWordGate.swiftpackages/swabble/Sources/SwabbleCore/HookExecutor.swift展示 Swift 原生应用、Go 工具链与 TypeScript Gateway 之间的依赖关系和通信集成点。三种语言通过共享的 AI 框架生态(pi 家族)和 IPC 协议实现互联互通。
Complexity: 18 nodes, 18 edges
Related files:
Swabble/Package.swiftapps/shared/OpenClawKit/Package.swiftapps/macos/Package.swiftscripts/docs-i18n/go.mod🔵 采用 Swift Actor 模型实现线程安全 (info)
SpeechPipeline 和 HookExecutor 都基于 Actor 实现,编译器强制保证并发安全,无需手动管理锁
🔵 模块化产品设计 (info)
三个独立产品(Swabble、SwabbleKit、SwabbleCLI)允许按需引入,移动应用无需依赖 CLI 模块
⚪ 较高的平台版本要求 (low)
macOS 15+、iOS 17+、CLI 更要求 macOS 26+,限制了在旧设备上的部署能力
🔵 launchd 集成提供企业级可靠性 (info)
通过 RunAtLoad 和 KeepAlive 属性实现自动启动和崩溃恢复
public actor SpeechPipeline {
public func start(locale: Locale, etiquette: Bool) async throws { ... }
public func stop() { ... }
}
public static func match(transcript:, segments:, config:) -> WakeWordGateMatch?
public actor HookExecutor {
public func execute(_ job: HookJob) async throws { ... }
}
packages/swabble/Package.swiftpackages/swabble/Sources/SwabbleCore/SpeechPipeline.swiftpackages/swabble/Sources/SwabbleKit/WakeWordGate.swiftpackages/swabble/Sources/SwabbleCLI/main.swiftpackages/swabble/Sources/SwabbleCore/HookExecutor.swiftpackages/swabble/Sources/SwabbleCore/SwabbleConfig.swiftOpenClaw 采用了一套精心设计的跨平台部署架构,支持七种以上的运行环境:macOS、iOS、Linux、Windows、Android、容器化平台(Docker/Podman)以及云服务(Fly.io、Render、DigitalOcean)。这种广泛的平台覆盖通过服务抽象层实现,使得核心业务逻辑与平台特定的服务管理机制完全解耦,极大地简化了跨平台维护的复杂性。
整个跨平台架构的核心是 src/daemon/service.ts 中定义的 GatewayService 抽象接口。该接口定义了一套统一的服务生命周期管理 API,包括 install、uninstall、stop、restart、isLoaded、readCommand 和 readRuntime 等方法。通过 resolveGatewayService() 函数,系统能够在运行时自动检测当前平台并返回对应的服务实现。
这种设计遵循了「策略模式」(Strategy Pattern),每个平台后端都实现了相同的接口,但内部采用平台原生的服务管理机制。readRuntime() 方法返回统一的运行时状态结构,包含 status、state、pid、lastExitStatus 和 lastRunTime 等字段,使得上层代码无需关心底层实现细节。
macOS 平台使用 Apple 原生的 launchd 服务管理器。实现代码位于 src/daemon/launchd.ts(442 行),通过 launchctl 命令行工具与系统交互。服务配置文件以 .plist 格式存储在 ~/Library/LaunchAgents/ 目录下,支持用户级别的自动启动和崩溃恢复。
// launchd 服务单元生成示例(概念性代码)
const launchPlist = {
Label: 'com.openclaw.gateway',
ProgramArguments: [nodePath, openclawPath, 'gateway'],
RunAtLoad: true,
KeepAlive: {
SuccessfulExit: false // 非正常退出时重启
},
WorkingDirectory: workDir,
StandardOutPath: logPath,
StandardErrorPath: errorLogPath
};
// 写入 ~/Library/LaunchAgents/com.openclaw.gateway.plist
// 执行: launchctl load -w <plist_path>
Linux 平台采用 systemd 用户级服务(user services),实现位于 src/daemon/systemd.ts(419 行)。服务单元文件存储在 ~/.config/systemd/user/ 目录下,通过 systemctl --user 命令进行管理。
# systemd 服务单元示例
[Unit]
Description=OpenClaw Gateway
After=network.target
[Service]
Type=simple
ExecStart=/path/to/node /path/to/openclaw.mjs gateway
WorkingDirectory=/path/to/openclaw
Environment=NODE_ENV=production
Restart=on-failure
KillMode=process
[Install]
WantedBy=default.target
关键技术点:User Lingering
systemd 用户服务有一个重要的限制:默认情况下,当用户注销后,其所有用户级服务都会被终止。为了让 Gateway 在用户注销后继续运行,必须启用 “lingering” 功能:
enableSystemdUserLinger(): 执行 loginctl enable-linger 命令readSystemdUserLingerStatus(): 检查 lingering 状态isSystemdUserServiceAvailable(): 检测当前环境是否支持 systemd 用户服务(WSL2 兼容性检测)Windows 平台使用计划任务(Scheduled Tasks)机制,实现位于 src/daemon/schtasks.ts(343 行)。通过 schtasks 命令行工具创建开机自启动任务,启动脚本存储在 %OPENCLAW_STATE%/gateway.cmd。
OpenClaw 提供了生产级的 Docker 镜像配置,基于 Node 22 运行时,使用 PNPM 进行依赖管理。容器采用非 root 用户运行以增强安全性,并提供多种变体:
对于偏好 rootless 容器的用户,项目提供了 Podman 支持。scripts/podman/openclaw.container.in 是一个 Quadlet 模板文件,可与 systemd 深度集成:
# Podman Quadlet 容器定义
[Container]
Image=openclaw:local
ContainerName=openclaw
UserNS=keep-id
Volume=/.openclaw:/home/node/.openclaw
EnvironmentFile=/.openclaw/.env
PublishPort=18789:18789
[Service]
TimeoutStartSec=300
Restart=on-failure
setup-podman.sh 脚本负责初始化 rootless Podman 环境并配置必要的用户命名空间映射。
Fly.io 是推荐的云部署平台之一,配置文件为 fly.toml。项目还提供了 fly.private.toml 用于私有部署场景:
# fly.toml - 公开部署配置
app = "openclaw"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
NODE_OPTIONS = "--max-old-space-size=1536"
[processes]
app = "node dist/index.js gateway --allow-unconfigured --port 3000 --bind lan"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = false
min_machines_running = 1
[[vm]]
size = "shared-cpu-2x"
memory = "2048mb"
[mounts]
source = "openclaw_data"
destination = "/data"
私有部署模式:fly.private.toml 移除了 [http_service] 配置块,使实例不分配公共入口。访问方式改为 fly proxy、WireGuard VPN 或 fly ssh,适用于需要隐藏在互联网扫描器之外的敏感部署。
Render 平台使用 render.yaml 进行声明式配置:
services:
- type: web
name: openclaw
runtime: docker
plan: starter
healthCheckPath: /health
envVars:
- key: PORT
value: "8080"
- key: SETUP_PASSWORD
sync: false
- key: OPENCLAW_GATEWAY_TOKEN
generateValue: true
disk:
name: openclaw-data
mountPath: /data
sizeGB: 1
无论选择哪个云平台,都存在一些共通的最佳实践:
状态持久化
[mounts] 配置持久卷disk 配置磁盘挂载/data 或 /data/.openclaw安全配置
OPENCLAW_GATEWAY_TOKEN: 自动生成的认证令牌--bind lan: 容器平台内部网络绑定/health 端点用于健康检查资源规格建议
--max-old-space-size=1536除了服务器端部署,OpenClaw 还提供了完整的原生移动应用:
macOS 应用 (apps/macos/)
iOS 应用 (apps/ios/)
Android 应用 (apps/android/)
scripts/systemd/ 目录包含用于生产环境监控的 systemd 单元:
# openclaw-auth-monitor.timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=30min
Persistent=true
# openclaw-auth-monitor.service
[Service]
Type=oneshot
ExecStart=/home/admin/openclaw/scripts/auth-monitor.sh
Environment=WARN_HOURS=2
该定时器每 30 分钟执行一次认证状态检查,在认证即将过期前 2 小时发出警告,确保服务连续性。
优势:
权衡:
🔵 完善的跨平台服务抽象 (info)
GatewayService 接口提供统一的服务生命周期管理 API,通过策略模式实现平台解耦
🔵 多云平台支持 (info)
提供 Fly.io 和 Render.com 的声明式配置,支持公开和私有部署模式
⚪ systemd lingering 依赖 (low)
Linux 用户服务需要手动启用 lingering,否则用户注销后服务会被终止
🔵 容器安全最佳实践 (info)
Docker/Podman 镜像使用非 root 用户运行,Podman 支持 rootless 模式
[Unit]
Description=OpenClaw Gateway
After=network.target
[Service]
Type=simple
ExecStart=/path/to/node /path/to/openclaw.mjs gateway
Environment=NODE_ENV=production
Restart=on-failure
[Install]
WantedBy=default.target
app = "openclaw"
primary_region = "iad"
[env]
NODE_ENV = "production"
NODE_OPTIONS = "--max-old-space-size=1536"
[http_service]
internal_port = 3000
force_https = true
min_machines_running = 1
[[vm]]
size = "shared-cpu-2x"
memory = "2048mb"
src/daemon/service.tssrc/daemon/launchd.tssrc/daemon/systemd.tssrc/daemon/schtasks.tsfly.tomlrender.yamlscripts/podman/openclaw.container.inscripts/systemd/openclaw-auth-monitor.timerOpenClaw 项目采用现代化的 Node.js 技术栈,基于 pnpm 工作空间的 monorepo 架构管理复杂的依赖关系。项目要求 Node.js >= 22.12.0,使用 ES 模块系统,体现了对最新 JavaScript 运行时特性的积极采纳。整体依赖策略展现了「前沿但稳定」的工程哲学——核心框架追求最新主要版本,同时通过精细的版本锁定和安全覆盖机制保障生产稳定性。
项目采用 Express 5.2.1 作为 HTTP 服务器框架,这是 Express 的最新主要版本(2024年发布)。Express 5 带来了原生 async/await 支持、改进的错误处理和更好的 Promise 集成,消除了早期版本中回调地狱的问题。
// Express 5 原生支持 async 路由处理
app.get('/api/status', async (req, res) => {
const status = await gateway.getHealthStatus();
res.json(status); // 无需 try-catch,Express 5 自动捕获异常
});
项目构建了多层次的运行时类型验证体系:
| 库 | 版本 | 用途 | 特点 |
|---|---|---|---|
| Zod | ^4.3.6 | 外部输入验证 | 最新主要版本,TypeScript 优先 |
| @sinclair/typebox | 0.34.48 | JSON Schema 生成 | 固定版本,API 稳定性 |
| AJV | ^8.18.0 | Gateway 协议验证 | 高性能 JSON Schema 验证 |
这种组合策略体现了务实的工程决策:Zod 用于开发者友好的 API 边界验证,而 AJV 用于高性能的内部协议验证(Gateway 的 90+ RPC 方法均使用 AJV 验证)。
项目采用了 Rust 生态的现代构建工具,显著提升编译性能:
{
"devDependencies": {
"tsdown": "^0.20.3", // 基于 Rust 的 TypeScript 打包器
"tsx": "^4.21.0", // TypeScript 执行器
"typescript": "^5.9.3", // 最新 TypeScript
"oxlint": "^1.47.0", // 基于 Rust 的 ESLint 替代
"oxfmt": "0.32.0" // 基于 Rust 的代码格式化
}
}
技术选型理由:oxlint 相比传统 ESLint 快 50-100 倍,tsdown 相比 webpack 快 10-20 倍,这对于包含 800+ TypeScript 文件的大型 monorepo 至关重要。
OpenClaw 作为多平台消息网关,集成了 7 大主流消息平台的官方或社区 SDK:
| 平台 | SDK | 版本 | 类型 |
|---|---|---|---|
| Telegram | grammy | ^1.40.0 | 现代 Bot 框架 |
| @whiskeysockets/baileys | 7.0.0-rc.9 | 逆向工程 Web API | |
| Discord | @buape/carbon | 0.14.0 | Bot 框架 |
| Slack | @slack/bolt | ^4.6.0 | 官方 App 框架 |
| LINE | @line/bot-sdk | ^10.6.0 | 官方 SDK |
| Lark/飞书 | @larksuiteoapi/node-sdk | ^1.59.0 | 官方 SDK |
| Matrix | @vector-im/matrix-bot-sdk | 0.8.0-element.3 | 社区 SDK + E2E 加密 |
// WhatsApp 使用逆向工程的 Web 协议
import { makeWASocket, useMultiFileAuthState } from '@whiskeysockets/baileys';
// 风险点:RC 版本、非官方 API、可能的协议变更
const sock = makeWASocket({
auth: state,
printQRInTerminal: true,
// 需要持久化认证状态以维持会话
});
风险级别:中等。Baileys 是活跃维护的项目,但作为逆向工程实现,存在 WhatsApp 协议变更导致中断的风险。项目通过固定 RC 版本(7.0.0-rc.9)来保障稳定性。
项目构建了全面的 AI 能力栈,支持云端和本地两种部署模式:
| 提供商 | SDK | 用途 |
|---|---|---|
| AWS Bedrock | @aws-sdk/client-bedrock ^3.990.0 | Claude、Titan 等模型 |
| OpenAI | openai ^6.22.0 | GPT 系列模型 |
| Google Gemini | OAuth 集成 | Gemini 模型 |
| Anthropic | 直接 API 调用 | Claude 模型 |
// 本地模型执行示例
import { LlamaModel, LlamaContext } from 'node-llama-cpp';
// node-llama-cpp 3.15.1:绑定 llama.cpp,支持 GGUF 模型
const model = new LlamaModel({ modelPath: './models/llama-7b.gguf' });
const context = new LlamaContext({ model });
// ollama ^0.6.3:通过 Ollama 服务运行本地模型
import { Ollama } from 'ollama';
const ollama = new Ollama();
await ollama.chat({ model: 'llama2', messages: [...] });
| 库 | 用途 | 状态 |
|---|---|---|
| lancedb ^0.26.2 | 主要向量存储 | 稳定 |
| sqlite-vec 0.1.7-alpha.2 | 轻量级嵌入 | Alpha 版本 ⚠️ |
注意:sqlite-vec 处于 alpha 阶段,生产环境应优先使用 LanceDB。
项目依赖的版本货币性呈现健康的分布模式:
| 状态 | 占比 | 代表性包 |
|---|---|---|
| ✅ 最新版本 | ~70% | Express 5, Zod 4, TypeScript 5.9, Vitest 4 |
| ⚠️ 有意固定 | ~20% | typebox, baileys, node-llama-cpp |
| 🔒 安全覆盖 | ~10% | tar, tough-cookie, qs |
项目使用 pnpm overrides 修补传递性依赖中的已知漏洞:
{
"pnpm": {
"overrides": {
"fast-xml-parser": "5.3.4", // XML 解析 CVE
"form-data": "2.5.4", // 原型污染
"qs": "6.14.2", // 查询字符串解析
"tar": "7.5.7", // 归档提取 CVE
"tough-cookie": "4.1.3" // Cookie 处理
},
"minimumReleaseAge": 2880 // 48小时发布延迟
}
}
安全实践亮点:
minimumReleaseAge: 2880 意味着新版本发布 48 小时后才会被 pnpm 安装,为社区发现供应链攻击提供缓冲时间onlyBuiltDependencies 限制可编译的原生模块,防止恶意包执行构建脚本OpenClaw 的扩展系统采用统一的依赖模式,所有扩展通过 pnpm workspace 协议依赖核心包:
{
"name": "@openclaw/telegram",
"version": "2026.2.15",
"private": true,
"type": "module",
"devDependencies": {
"openclaw": "workspace:*"
},
"peerDependencies": {
"openclaw": ">=2026.1.26"
},
"openclaw": {
"extensions": ["./index.ts"]
}
}
架构优势:
pnpm audit 验证漏洞状态🔵 采用现代化前沿技术栈 (info)
Express 5、Zod 4、TypeScript 5.9 等均为最新主要版本,展现积极的技术更新策略
🔵 完善的安全覆盖机制 (info)
通过 pnpm overrides 修补 5 个已知 CVE,48 小时发布延迟策略降低供应链攻击风险
⚪ WhatsApp SDK 使用逆向工程实现 (medium)
@whiskeysockets/baileys 7.0.0-rc.9 为 RC 版本,存在协议变更风险
⚪ sqlite-vec 处于 Alpha 阶段 (low)
sqlite-vec 0.1.7-alpha.2 不建议用于生产环境关键路径
app.get('/api/status', async (req, res) => {
const status = await gateway.getHealthStatus();
res.json(status);
});
{
"pnpm": {
"overrides": {
"fast-xml-parser": "5.3.4",
"tar": "7.5.7",
"tough-cookie": "4.1.3"
},
"minimumReleaseAge": 2880
}
}
package.jsonpnpm-workspace.yamlextensions/telegram/package.jsonextensions/memory-lancedb/package.jsonOpenClaw 项目采用多语言架构,其中 Swift 负责原生 macOS/iOS 应用开发,Go 则用于文档国际化工具链。这种技术选型体现了”用正确的工具做正确的事”的工程哲学——Swift 提供流畅的 Apple 平台原生体验,Go 则以其优秀的并发模型和跨平台编译能力支撑批处理工具。本节深入分析这两种语言的依赖结构、平台要求及跨语言集成点。
项目包含三个独立的 Swift Package,形成了清晰的职责分层:
| Package | 定位 | 平台要求 | 外部依赖数 |
|---|---|---|---|
| Swabble | 语音唤醒守护进程 | macOS 15+, iOS 17+ | 2 |
| OpenClawKit | 共享 SDK 框架 | macOS 15+, iOS 18+ | 2 |
| OpenClaw macOS | 菜单栏应用 | macOS 15+ | 7 |
Swabble Package 是语音唤醒系统的核心,使用 Swift 6.2 的严格并发模式(Strict Concurrency)确保线程安全。它提供三个产品:Swabble 核心库、SwabbleKit 唤醒词工具和 swabble CLI 守护进程。值得注意的是,该包仅依赖两个外部库——精确版本锁定的 Commander 0.2.1(steipete 维护的 fork)用于 CLI 参数解析,以及 Apple 官方的 swift-testing 用于新一代测试框架。这种极简依赖策略降低了维护负担和安全风险。
// Swabble/Package.swift - 严格并发与最小依赖
let package = Package(
name: "swabble",
platforms: [.macOS(.v15), .iOS(.v17)],
products: [
.library(name: "Swabble", targets: ["SwabbleCore"]),
.library(name: "SwabbleKit", targets: ["SwabbleKit"]),
.executable(name: "swabble", targets: ["SwabbleCLI"])
],
dependencies: [
.package(url: "https://github.com/steipete/Commander.git", exact: "0.2.1"),
.package(url: "https://github.com/apple/swift-testing.git", from: "0.99.0")
]
)
OpenClawKit Package 作为共享 SDK,为 macOS 和 iOS 应用提供统一的协议定义和 UI 组件。它包含三个库:OpenClawProtocol(IPC 协议定义)、OpenClawKit(核心客户端 SDK)和 OpenClawChatUI(聊天 UI 组件)。依赖方面引入了 ElevenLabsKit 0.1.0 用于文字转语音(TTS)集成,以及 textual 0.3.1 用于 Markdown 渲染。所有 target 均启用了严格并发检查,体现了对 Swift 6 并发安全的全面拥抱。
OpenClaw macOS Package 是依赖最丰富的包,作为最终用户产品需要整合多种功能。其依赖包括:
依赖版本管理采用混合策略:
| 策略 | 示例 | 适用场景 |
|---|---|---|
| 精确版本 (exact) | Commander 0.2.1 | Fork 维护、API 稳定性关键 |
| 最小版本 (from) | swift-log ^1.8.0 | 官方库、语义化版本可信 |
| 分支锁定 (branch) | Peekaboo main | 活跃开发、需要最新特性 |
这种策略平衡了稳定性与灵活性。精确版本用于 steipete 维护的 fork(如 Commander、ElevenLabsKit),确保不会意外升级到不兼容版本。官方库(swift-log、swift-subprocess)使用语义化版本范围,享受 bug 修复的同时保持 API 兼容。Peekaboo 使用 main 分支表明该依赖仍在活跃开发中,需要密切跟踪上游变化。
Go 在项目中的角色是构建工具和脚本,而非运行时组件。scripts/docs-i18n/ 模块专门用于文档国际化处理,采用 Go 1.24.0(2025 年 2 月发布的最新版本)。
// scripts/docs-i18n/go.mod
module github.com/openclaw/openclaw/scripts/docs-i18n
go 1.24.0
require (
github.com/joshp123/pi-golang v0.0.4 // AI 翻译集成
github.com/yuin/goldmark v1.7.8 // Markdown 解析
golang.org/x/net v0.50.0 // HTML 处理
gopkg.in/yaml.v3 v3.0.1 // YAML frontmatter
)
依赖选型分析:
pi-golang v0.0.4:这是”pi”AI 框架的 Go 实现,与 TypeScript 核心使用的 @mariozechner/pi-agent-core 属于同一生态。版本号 0.0.x 表明仍处于早期开发阶段,但作为离线工具可接受一定的 API 不稳定性。
goldmark v1.7.8:高性能 Markdown 解析器,完全兼容 CommonMark 和 GFM 规范。其基于 AST 的可扩展设计非常适合文档处理管道。
golang.org/x/net v0.50.0:Go 官方扩展网络库,用于 HTML 解析和操作。版本 0.50.0 相当新,表明项目保持了依赖更新。
yaml.v3 v3.0.1:YAML 解析行业标准库,用于处理 Markdown frontmatter(元数据头)。
Go 工具实现了一个 AI 辅助的翻译管道:
docs/*.md 源文件
↓
goldmark: 解析 Markdown AST
↓
yaml.v3: 提取/更新 frontmatter
↓
pi-golang: AI 驱动翻译
↓
i18n 输出文件
这种设计将 AI 能力引入文档工作流,同时保持与主应用运行时的隔离。Go 的编译输出可以分发为独立二进制文件,简化了 CI/CD 集成。
| 组件 | macOS | iOS | Node.js | Go |
|---|---|---|---|---|
| 最低版本 | 15 (Sequoia) | 17-18 | 22.12.0 | 1.24.0 |
| AI 框架 | ElevenLabsKit | ElevenLabsKit | pi-agent-core | pi-golang |
| 服务管理 | launchd | N/A | systemd/schtasks | N/A |
| 更新机制 | Sparkle | App Store | N/A | N/A |
项目在不同语言中使用了同一 AI 框架的不同实现:
| 语言 | 包名 | 版本 | 成熟度 |
|---|---|---|---|
| TypeScript | @mariozechner/pi-agent-core | 0.52.12 | 稳定 |
| TypeScript | @mariozechner/pi-ai | 0.52.12 | 稳定 |
| Go | joshp123/pi-golang | 0.0.4 | 早期 |
TypeScript 实现作为核心运行时已相当成熟(0.52.x),而 Go 实现(0.0.x)仅用于离线工具,因此较低的成熟度可以接受。
Swift 原生应用与 TypeScript Gateway 通过以下方式集成:
apps/macos/Package.swift 直接引用工作区内的 OpenClawKit 和 swabble 包// apps/macos/Package.swift - 跨包集成
.executableTarget(
name: "OpenClaw",
dependencies: [
.product(name: "OpenClawProtocol", package: "OpenClawKit"),
.product(name: "OpenClawChatUI", package: "OpenClawKit"),
.product(name: "SwabbleKit", package: "swabble"),
.product(name: "PeekabooBridge", package: "Peekaboo"),
]
)
🔵 多语言职责分离清晰 (info)
Swift 处理原生应用,Go 处理工具链,TypeScript 处理服务端,各司其职
🔵 Swift 依赖极简化 (info)
Swabble 核心包仅 2 个外部依赖,有效降低维护负担和安全风险
⚪ Peekaboo 使用 main 分支依赖 (low)
分支依赖可能引入 breaking changes,建议切换到稳定 tag 版本
⚪ pi-golang 处于早期版本 (low)
版本 0.0.4 表明 API 可能不稳定,但作为离线工具影响有限
⚪ 平台最低版本要求较高 (medium)
macOS 15+ 和 iOS 18+ 要求可能限制用户覆盖范围
let package = Package(
name: "swabble",
platforms: [.macOS(.v15), .iOS(.v17)],
dependencies: [
.package(url: "https://github.com/steipete/Commander.git", exact: "0.2.1"),
.package(url: "https://github.com/apple/swift-testing.git", from: "0.99.0")
]
)
require (
github.com/joshp123/pi-golang v0.0.4 // AI 翻译集成
github.com/yuin/goldmark v1.7.8 // Markdown 解析
golang.org/x/net v0.50.0 // HTML 处理
gopkg.in/yaml.v3 v3.0.1 // YAML frontmatter
)
Swabble/Package.swiftapps/shared/OpenClawKit/Package.swiftapps/macos/Package.swiftscripts/docs-i18n/go.mod展示 A2UI 在 OpenClaw 中的完整集成架构,包括构建管道、Gateway 服务、渲染器和客户端桥接
Complexity: 15 nodes, 15 edges
Related files:
🔵 A2UI 采用声明式安全架构 (info)
AI 只能发送声明式 JSON,所有组件来自受信任目录,从根本上防止恶意代码注入
⚪ 版本锁定在 v0.8 预览版 (medium)
当前使用的是公开预览版,v1.0 稳定版发布前可能还有结构变更,需持续监控
⚪ v0.9 存在破坏性变更 (low)
v0.9 引入不兼容的消息格式变更(createSurface vs beginRendering),迁移需全面更新
🔵 Vendor 模式需手动同步 (info)
A2UI 作为 vendor 依赖而非 npm 包,需要手动运行构建脚本同步更新
{"surfaceUpdate": {"surfaceId": "main", "components": [{"id": "root", "component": {"Column": {"children": {"explicitList": ["title"]}}}}, {"id": "title", "component": {"Text": {"text": {"literalString": "Hello"}, "usageHint": "h1"}}}]}}
if (actionKeys[0] === 'createSurface') sawV09 = true;
if (sawV08 && sawV09) {
errors.push('mixed A2UI v0.8 and v0.9 messages');
}
vendor/a2ui/vendor/a2ui/renderers/lit/src/0.8/model-processor.tssrc/canvas-host/a2ui.tssrc/cli/nodes-cli/a2ui-jsonl.tsscripts/bundle-a2ui.sh展示项目中类型定义、运行时验证、模块组织之间的关系,以及类型安全如何贯穿整个代码生命周期。
Complexity: 17 nodes, 17 edges
Related files:
🔵 严格 TypeScript 配置 (info)
项目启用了完整的严格模式配置,包括 strict: true, ES2023 目标, NodeNext 模块系统
🔵 Schema-Type 配对模式 (info)
100+ AJV 验证器与 TypeScript 类型配对,确保编译时和运行时验证一致性
🔵 极低的 any 使用率 (info)
1000+ 文件中仅发现 7 处 any 使用,均有明确的 lint 禁用说明
⚪ 大参数对象函数 (low)
runReplyAgent 等函数有 20+ 参数,可考虑引入 Builder 模式改进
⚪ 协议索引文件过大 (low)
gateway/protocol/index.ts 包含 604 LOC 和 100+ 导入导出,可拆分为子模块
import type { ChannelId } from "../channels/plugins/types.js";
import type { RuntimeEnv } from "../runtime.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
export type ChannelPlugin<ResolvedAccount = any, Probe = unknown, Audit = unknown> = {
id: ChannelId;
meta: ChannelMeta;
capabilities: ChannelCapabilities;
config: ChannelConfigAdapter<ResolvedAccount>;
setup?: ChannelSetupAdapter;
pairing?: ChannelPairingAdapter;
security?: ChannelSecurityAdapter<ResolvedAccount>;
};
const ajv = new (AjvPkg as unknown as new (opts?) => import("ajv").default)({
allErrors: true,
strict: false,
removeAdditional: false,
});
export const validateConnectParams = ajv.compile<ConnectParams>(ConnectParamsSchema);
export function isInternalMessageChannel(raw?: string | null): raw is InternalMessageChannel {
return normalizeMessageChannel(raw) === INTERNAL_MESSAGE_CHANNEL;
}
tsconfig.jsonsrc/gateway/protocol/index.tssrc/channels/plugins/types.tssrc/config/types.base.tssrc/logging/subsystem.tsOpenClaw 项目构建了一套成熟的错误处理与日志记录体系,采用结构化日志、事件驱动诊断和敏感数据脱敏等现代可观测性实践。本章节深入分析这套系统的架构设计、实现模式以及存在的改进空间,帮助开发团队理解如何有效利用现有基础设施进行调试和监控。
项目的日志系统位于 src/logging/ 目录,由 6 个专职模块组成,总计约 1500 行代码。核心技术栈采用 tslog 库,支持 7 个日志级别(silent、fatal、error、warn、info、debug、trace),并实现了双输出通道——文件采用 JSON 格式便于机器解析,控制台则提供多种人类友好的显示样式。
// 子系统日志器创建模式 - src/logging/subsystem.ts
const log = createSubsystemLogger("gateway");
const logHealth = log.child("health");
// 使用示例:带结构化元数据的日志
log.info("Channel started", {
channelId: "telegram",
accountId: "bot-123",
startTime: Date.now()
});
// 层级命名支持:gateway/health/checks
logHealth.debug("Health check completed", { latency: 45 });
这种子系统日志器模式(Subsystem Logger Pattern)是项目的核心设计决策。每个子系统(如 gateway、agent、channel)拥有独立的日志实例,支持颜色编码的控制台输出(cyan、green、yellow、blue、magenta、red),并可通过 .child() 方法创建层级结构。这种设计使得在复杂的多组件系统中追踪问题来源变得直观高效。
文件日志采用每日滚动策略,命名格式为 openclaw-YYYY-MM-DD.log,存储在系统临时目录中。每条日志记录为独立的 JSON 行,包含 ISO 格式时间戳,支持自动清理 24 小时以上的历史日志。系统还支持外部传输器注册,便于集成第三方日志服务:
// 文件日志配置 - src/logging/logger.ts
interface FileLogConfig {
format: "json"; // 固定 JSON 格式
rolling: "daily"; // 每日滚动
maxAge: "24h"; // 自动清理
directory: os.tmpdir(); // 系统临时目录
}
// 支持外部集成
registerExternalTransport({
name: "datadog",
send: async (logs) => { /* 发送到 Datadog */ }
});
控制台日志则根据运行环境自动适配:TTY 环境使用 pretty 样式(带颜色和格式化),非 TTY 环境使用 compact 样式,也可强制使用 json 样式便于管道处理。系统还实现了 EPIPE/EIO 错误处理,防止管道断开导致的崩溃。
项目采用多种错误处理策略,根据场景选择最适合的模式:
1. Result 模式(函数式错误处理)
在 Gateway Hooks 等需要优雅处理可恢复错误的场景中,广泛使用 Result 类型:
// Result 模式 - 用于可恢复错误
type Result<T> =
| { ok: true; value: T }
| { ok: false; error: string };
async function readJsonBody(req: Request): Promise<Result<unknown>> {
const contentLength = parseInt(req.headers["content-length"] || "0");
if (contentLength > MAX_PAYLOAD_SIZE) {
return { ok: false, error: "payload too large" };
}
try {
const body = await req.json();
return { ok: true, value: body };
} catch (err) {
return { ok: false, error: "invalid JSON format" };
}
}
// 调用方处理
const result = await readJsonBody(req);
if (!result.ok) {
return res.status(400).json({ error: { message: result.error } });
}
const data = result.value; // TypeScript 自动推断类型
这种模式的优势在于强制调用方处理错误情况,避免未捕获异常,同时保持类型安全。
2. HTTP 错误响应格式
对外 API 遵循统一的错误响应格式,便于客户端解析:
// 标准 HTTP 错误响应格式
interface ErrorResponse {
error: {
message: string;
type: "unauthorized" | "invalid_request_error" | "rate_limited";
}
}
// 状态码映射
// 400 - Bad Request(请求格式错误)
// 401 - Unauthorized(认证失败)
// 408 - Request Timeout(请求超时)
// 413 - Payload Too Large(请求体过大)
// 429 - Rate Limited(速率限制)
3. 错误上下文保留
项目注重保留错误上下文,便于调试:
项目实现了事件驱动的诊断系统(src/infra/diagnostic-events.ts),这是实现全链路可观测性的核心组件。系统定义了 12 种诊断事件类型,覆盖从 LLM 调用到消息处理的完整生命周期:
| 事件类型 | 用途 | 关键数据 |
|---|---|---|
model.usage |
LLM Token 使用追踪 | tokens, cost, model |
webhook.received/processed/error |
Webhook 生命周期 | channel, latency, error |
message.queued/processed |
消息管道追踪 | sessionKey, queueDepth |
session.state/stuck |
会话状态机监控 | state, duration |
queue.lane.enqueue/dequeue |
命令队列操作 | lane, taskId |
run.attempt |
Agent 运行追踪 | runId, model, success |
diagnostic.heartbeat |
系统健康检查 | activeSessions, queueDepth |
心跳系统每 30 秒发送一次健康检查事件,追踪 Webhook 收发统计、活跃/等待会话数量和队列深度。会话状态追踪支持卡住检测(处理状态超过 2 分钟触发告警),并实现了 30 分钟 TTL 的会话自动清理。
安全性是日志系统的重要考量。src/logging/redact.ts 实现了 16 种默认脱敏模式,自动识别并掩盖敏感信息:
// 脱敏模式示例
const REDACT_PATTERNS = [
// 环境变量:*KEY, *TOKEN, *SECRET, *PASSWORD
/^(\w+_(KEY|TOKEN|SECRET|PASSWORD))=(.+)$/,
// JSON 字段:apiKey, token, secret, password, accessToken
/"(apiKey|token|secret|password|accessToken)"\s*:\s*"([^"]+)"/,
// CLI 参数:--api-key, --token, --secret
/--(api-key|token|secret)\s+(\S+)/,
// Auth 头:Bearer tokens
/Bearer\s+([A-Za-z0-9_-]+)/,
// 已知前缀:sk-, ghp_, github_pat_, xox[baprs]-, gsk_, AIza, pplx-, npm_
/(sk-|ghp_|github_pat_|xox[baprs]-|gsk_|AIza|pplx-|npm_)[A-Za-z0-9_-]+/
];
// 脱敏输出格式
// 长 token: "sk-abc...xyz9" (保留前6后4)
// 短 token: "***"
// PEM 块: 保留 BEGIN/END 行,内容替换为 [REDACTED]
这套脱敏系统确保敏感信息不会意外泄露到日志文件中,即使在调试模式下也能安全使用。
尽管整体架构成熟,分析发现了若干可改进之处:
1. 静默错误处理(高优先级)
代码库中存在 50+ 处静默错误处理,完全不记录任何日志:
// 问题模式:静默吞噬错误
try {
await optionalCleanup();
} catch { /* silently ignore */ }
// 建议:至少记录 trace 级别日志
try {
await optionalCleanup();
} catch (err) {
log.trace("Optional cleanup failed", { error: err.message });
}
2. 缺少错误码枚举
当前错误消息为自由文本,建议引入数字错误码便于监控告警:
// 建议的错误码体系
enum ErrorCode {
VALIDATION_FAILED = 1001,
AUTH_EXPIRED = 2001,
RATE_LIMITED = 2002,
CHANNEL_DISCONNECTED = 3001,
// ...
}
3. 缺少分布式追踪 ID
当前系统没有全局 traceId,跨组件追踪困难。建议在请求入口生成 traceId 并在整个处理链路中传递。
4. 生产环境堆栈信息丢失
生产日志省略了堆栈跟踪和 cause 信息,建议在文件日志中保留完整错误链。
OpenClaw 的错误处理与日志系统展现了成熟的架构设计:结构化日志、事件驱动诊断、敏感数据脱敏等特性为可观测性奠定了坚实基础。Result 模式在可恢复错误场景的应用体现了函数式编程思想,子系统日志器的层级设计则为复杂系统的调试提供了清晰的视角。
主要改进方向集中在减少静默错误、引入错误码枚举和分布式追踪 ID,这些改进将进一步提升生产环境的可观测性和问题定位效率。
🔵 成熟的结构化日志架构 (info)
采用 tslog 库实现 7 级日志、双输出通道(JSON 文件 + 可视化控制台)、子系统日志器模式,支持层级命名和颜色编码
🔵 完善的诊断事件系统 (info)
12 种诊断事件类型覆盖完整生命周期,30 秒心跳检测,会话状态追踪和卡住检测
🔵 敏感数据自动脱敏 (info)
16 种脱敏模式自动识别 API Key、Token、密码等敏感信息,确保日志安全
⚪ 静默错误处理过多 (medium)
代码库中存在 50+ 处静默吞噬错误的 try-catch 块,建议改为 trace/debug 级别日志
⚪ 缺少分布式追踪 ID (low)
当前无全局 traceId,跨组件追踪困难,建议在请求入口生成并传递 traceId
⚪ 缺少错误码枚举 (low)
错误消息为自由文本,不便于监控告警,建议引入数字错误码体系
const log = createSubsystemLogger("gateway");
const logHealth = log.child("health");
log.info("Channel started", { channelId: "telegram" });
type Result<T> = { ok: true; value: T } | { ok: false; error: string };
async function readJsonBody(req): Promise<Result<unknown>> {
if (contentLength > MAX_PAYLOAD_SIZE) {
return { ok: false, error: "payload too large" };
}
return { ok: true, value: await req.json() };
}
// 已知前缀自动识别:sk-, ghp_, github_pat_, xox[baprs]-
// 长 token: "sk-abc...xyz9" (保留前6后4)
// 短 token: "***"
src/logging/logger.tssrc/logging/subsystem.tssrc/logging/console.tssrc/logging/diagnostic.tssrc/logging/redact.tssrc/infra/diagnostic-events.ts代码库中存在多种类型的死代码和技术债务,包括废弃导出、类型不安全文件、空遗留常量以及桶导出模式。本节深入分析这些问题的分布、影响以及清理建议。
通过对整个 src/ 目录的静态分析,识别出以下死代码分布:
| 类型 | 数量 | 严重程度 | 影响评估 |
|---|---|---|---|
| @deprecated 标注导出 | 17 | 中等 | 需迁移消费者代码 |
| @ts-nocheck 文件 | 2 (1877 LOC) | 高 | 运行时错误风险 |
| 遗留文件 | 10 | 中等 | 增加维护负担 |
| 空遗留数组 | 5 | 低 | 可直接移除 |
| 桶导出文件 | 15+ | 低 | 模块组织开销 |
这些死代码约占代码库 2-3% 的体积,虽然不影响运行时功能,但增加了理解成本和维护负担。
项目中存在 17 个使用 @deprecated JSDoc 标注的导出,这些导出仍在代码中保留但已不推荐使用。主要分布在三个子系统:
配置模块 (src/config/types.*.ts) 包含最多的废弃导出,反映了配置结构的演进历史:
// src/config/types.messages.ts:44
// @deprecated - 音频模型配置已移至新位置
tools.media.audio.models: AudioModelConfig;
// src/config/types.messages.ts:53
// @deprecated - 使用新的消息前缀配置
whatsapp.messagePrefix: string;
// src/config/types.base.ts:81
// @deprecated - 将 'dm' 迁移为 'direct'
dm: DirectMessageConfig;
根本原因:配置结构重构时保留了向后兼容性,但未设置移除时间表。
影响:消费这些配置的代码可能使用过时的路径,导致配置不生效或行为不一致。
插件 SDK 中存在三个废弃类型,影响第三方插件开发:
// src/plugin-sdk/index.ts:81
// @deprecated - 使用标准 Config 类型
export type OpenClawConfig = LegacyConfig;
// src/plugin-sdk/index.ts:139
// @deprecated - 使用新的 ChatType 枚举
export type ChatType = 'dm' | 'group' | 'channel';
// src/plugins/runtime/types.ts:227
// @deprecated - BodyForAgent 明文信封模式已弃用
export interface BodyForAgent { ... }
建议:在下一个主版本(semver major)中移除这些类型,并在 CHANGELOG 中提供迁移指南。
这是最高优先级的技术债务。两个核心文件使用 @ts-nocheck 完全禁用了 TypeScript 类型检查:
// @ts-nocheck
// oxlint-disable eslint/no-unused-vars, typescript/no-explicit-any
import Database from 'node:sqlite';
import chokidar from 'chokidar';
export class MemoryManagerSyncOps {
private db: any; // 缺少类型定义
private watcher: any; // 文件监视器无类型
async indexFile(path: string) {
// 1078 行代码无类型检查
const result = this.db.prepare(...).run(...);
return result; // 返回类型未知
}
}
风险分析:
node:sqlite 同步 API,参数类型错误在编译期无法捕获// @ts-nocheck
// oxlint-disable eslint/no-unused-vars, typescript/no-explicit-any
const EMBEDDING_BATCH_MAX_TOKENS = 8000;
export class MemoryManagerEmbeddingOps {
async generateEmbedding(text: string): Promise<any> {
// 调用 OpenAI / Gemini / Voyage 嵌入 API
// 799 行代码处理向量操作,无类型安全
const vectors = await this.openai.embeddings.create(...);
return vectors.data[0].embedding; // 可能出现 undefined 访问
}
}
风险分析:
修复策略:建议分阶段添加类型:
@ts-nocheckany,添加具体类型@ts-nocheck,启用严格模式src/config/ 目录包含 10 个遗留相关文件,总计约 2000 LOC:
| 文件 | 用途 | 状态 |
|---|---|---|
legacy.ts |
核心迁移逻辑 | 活跃使用 |
legacy.rules.ts |
25+ 迁移规则 | 活跃使用 |
legacy.migrations.ts |
增量迁移 | 活跃使用 |
legacy-migrate.ts |
迁移编排 | 活跃使用 |
legacy.shared.ts |
共享类型 | 活跃使用 |
cron/legacy-delivery.ts |
传统投递处理 | 需评估 |
compat/legacy-names.ts |
空遗留名称 | 可移除 |
这些文件中,compat/legacy-names.ts 包含已完成迁移的空数组:
// src/compat/legacy-names.ts
export const PROJECT_NAME = "openclaw" as const;
export const LEGACY_PROJECT_NAMES = [] as const; // ← 空数组
export const MANIFEST_KEY = PROJECT_NAME;
export const LEGACY_MANIFEST_KEYS = LEGACY_PROJECT_NAMES; // ← 引用空数组
export const LEGACY_PLUGIN_MANIFEST_FILENAMES = [] as const; // ← 空数组
export const LEGACY_CANVAS_HANDLER_NAMES = [] as const; // ← 空数组
export const LEGACY_MACOS_APP_SOURCES_DIRS = [] as const; // ← 空数组
分析:这些常量曾用于项目重命名/迁移,现在所有遗留数组均为空,表明迁移已完成。
清理方案:
LEGACY_* 常量的所有引用// src/memory/openai-batch.ts
// Deprecated: use ./batch-openai.js
export * from "./batch-openai.js";
这是一个单行垫片文件,用于向后兼容。应在确认无外部消费者后移除。
// src/browser/client-actions.ts (4 个重导出)
export * from "./client-actions-core.js";
export * from "./client-actions-observe.js";
export * from "./client-actions-state.js";
export * from "./client-actions-types.js";
// src/browser/pw-tools-core.ts (8 个重导出)
export * from "./pw-tools-core.activity.js";
export * from "./pw-tools-core.downloads.js";
// ... 6 more modules
这些桶导出是有意的模块聚合模式,不是死代码,但需要注意 tree-shaking 影响。
项目使用 _test 对象模式暴露内部函数供测试使用:
// src/tts/tts.ts (942 LOC)
export const _test = {
isValidVoiceId,
isValidOpenAIVoice,
isValidOpenAIModel,
OPENAI_TTS_MODELS,
OPENAI_TTS_VOICES,
parseTtsDirectives,
resolveModelOverridePolicy,
summarizeText,
resolveOutputFormat,
resolveEdgeOutputFormat,
};
模式说明:这种模式允许测试私有函数而无需直接导出它们。这是有意设计,不应视为死代码,但表明测试策略的特殊性。
根据影响和风险,建议按以下顺序清理:
openai-batch.ts 废弃重导出
legacy-names.ts 空数组
⚪ @ts-nocheck 文件禁用类型检查 (high)
两个核心内存模块文件(共 1877 LOC)完全禁用 TypeScript 类型检查,存在运行时错误风险
⚪ 17 个废弃导出待迁移 (medium)
配置类型和 Plugin SDK 中存在 17 个 @deprecated 标注的导出,需要制定迁移计划
⚪ 空遗留常量可移除 (low)
legacy-names.ts 中的 5 个空数组表明迁移已完成,可安全删除
⚪ 废弃重导出文件 (low)
openai-batch.ts 是单行垫片文件,确认无消费者后可移除
// @ts-nocheck
// oxlint-disable eslint/no-unused-vars, typescript/no-explicit-any
export class MemoryManagerSyncOps {
private db: any; // 缺少类型定义
async indexFile(path: string) {
const result = this.db.prepare(...).run(...);
return result; // 返回类型未知
}
}
// src/compat/legacy-names.ts
export const PROJECT_NAME = "openclaw" as const;
export const LEGACY_PROJECT_NAMES = [] as const; // ← 空数组
export const LEGACY_PLUGIN_MANIFEST_FILENAMES = [] as const; // ← 空数组
export const LEGACY_CANVAS_HANDLER_NAMES = [] as const; // ← 空数组
// src/tts/tts.ts
export const _test = {
isValidVoiceId,
isValidOpenAIVoice,
parseTtsDirectives,
resolveModelOverridePolicy,
// 允许测试私有函数而无需直接导出
};
src/memory/manager-sync-ops.tssrc/memory/manager-embedding-ops.tssrc/compat/legacy-names.tssrc/memory/openai-batch.tssrc/config/types.messages.tssrc/plugin-sdk/index.tssrc/tts/tts.tsOpenClaw 实现了一套复杂的多层身份认证和授权系统,旨在保护网关访问和各消息渠道的安全。该系统采用深度防御策略,通过组合多种认证机制、速率限制和细粒度的访问控制来实现全面的安全保护。本节将深入分析这些安全机制的实现细节、设计权衡及潜在风险点。
网关是 OpenClaw 的核心入口点,提供 WebSocket 和 HTTP 两种访问方式。为了保护网关免受未授权访问,系统实现了五种不同的认证模式,每种模式适用于不同的部署场景。
这是最常用的认证方式,适合生产环境部署。Token 通过 gateway.auth.token 配置项或 OPENCLAW_GATEWAY_TOKEN 环境变量设置。认证逻辑实现在 src/gateway/auth.ts 模块中:
// src/gateway/auth.ts - Token 认证核心逻辑
export function resolveGatewayAuth(config: GatewayConfig): GatewayAuth {
const token = config.auth?.token ?? process.env.OPENCLAW_GATEWAY_TOKEN;
const password = config.auth?.password ?? process.env.OPENCLAW_GATEWAY_PASSWORD;
// Token 和 Password 互斥,只能使用其中一种
if (token && password) {
throw new ConfigError('Cannot use both token and password authentication');
}
return {
mode: token ? 'token' : password ? 'password' : 'none',
secret: token ?? password,
};
}
export function authorizeGatewayConnect(auth: GatewayAuth, providedSecret: string): boolean {
if (!auth.secret) return true; // 无认证模式
// 使用时序安全的字符串比较,防止时序攻击
return safeEqualSecret(auth.secret, providedSecret);
}
关键安全特性:系统使用 safeEqualSecret() 函数进行密钥比较,这是一个时序安全(timing-safe)的字符串比较实现。传统的字符串比较 === 会在发现第一个不匹配字符时立即返回,攻击者可以通过测量响应时间来逐字符猜测密钥。时序安全比较确保无论密钥是否匹配,比较操作的执行时间都相同。
对于部署在 Tailscale 网络中的实例,系统支持使用 Tailscale 的身份验证机制。这种模式通过 Tailscale WHOIS API 验证连接用户的身份:
// Tailscale 认证流程
// 1. 用户通过 Tailscale 网络连接
// 2. 系统从 HTTP 头 'tailscale-user-login' 获取用户身份
// 3. 调用 Tailscale WHOIS API 验证身份
// 4. 检查 gateway.tailscale.mode 配置
// - 'serve': 仅内部 tailnet 访问
// - 'funnel': 允许公网访问(危险!)
安全警告:tailscale.mode='funnel' 配置会将网关暴露到公网,这是一个 CRITICAL 级别的安全风险。审计系统会在检测到此配置时生成 tailscale_funnel 级别为 critical 的告警。
当 OpenClaw 部署在反向代理(如 nginx、Pomerium)后面时,可以信任代理传递的用户身份信息。配置 gateway.auth.trustedProxy.userHeader 指定包含用户身份的 HTTP 头名称,系统同时进行 IP 白名单验证,确保请求确实来自可信的代理服务器。
这是为 Control UI 设计的配对认证机制。当用户首次从新设备访问控制界面时,需要使用显示在终端中的一次性配对码进行设备绑定。device-token 端点受到专门的速率限制保护。
为防止暴力破解和拒绝服务攻击,网关实现了精细的速率限制策略:
// src/gateway/auth-rate-limit.ts - 速率限制配置
const RATE_LIMIT_CONFIG = {
maxAttempts: 10, // 每分钟最多 10 次尝试
windowMs: 60 * 1000, // 时间窗口 1 分钟
lockoutMs: 5 * 60 * 1000, // 锁定时间 5 分钟
cleanupIntervalMs: 60 * 1000, // 清理间隔 60 秒
};
// 本地回环请求豁免速率限制
function isLocalDirectRequest(req: IncomingMessage): boolean {
const remoteAddress = req.socket.remoteAddress;
const isLoopback = remoteAddress === '127.0.0.1' || remoteAddress === '::1';
// 检查是否存在可疑的代理头(防止伪造)
const hasSuspiciousHeaders = req.headers['x-forwarded-for'] ||
req.headers['x-real-ip'];
return isLoopback && !hasSuspiciousHeaders;
}
设计权衡:本地请求(localhost)被豁免速率限制,这是为了方便开发调试。但系统同时检查是否存在 X-Forwarded-For 等代理头,防止远程请求伪装成本地请求绑过限制。
每个消息渠道(WhatsApp、Telegram、Discord、Slack 等)都有独立的认证和授权配置,这种设计允许为不同渠道设置不同的安全策略。
Discord 使用 Bot Token 认证,支持 Guild(服务器)和 Channel(频道)级别的访问控制:
# config.yaml - Discord 安全配置示例
channels:
discord:
token: ${DISCORD_BOT_TOKEN} # 使用环境变量,不要直接写入配置
allowFrom:
guilds:
- "123456789012345678" # 允许的 Guild ID
channels:
- "987654321098765432" # 允许的 Channel ID
commands:
useAccessGroups: true # 启用访问组控制(重要!)
dmPolicy: "allowlist" # DM 策略:仅允许白名单用户
关键配置:commands.useAccessGroups 默认为 false,意味着任何能访问 Bot 的用户都可以执行所有命令。这是一个 CRITICAL 级别的安全问题,生产环境必须设置为 true。
Telegram 使用数字 ID 进行用户和群组识别:
channels:
telegram:
token: ${TELEGRAM_BOT_TOKEN}
allowFrom:
users:
- 123456789 # 必须是数字 ID,不是用户名
groups:
- -1001234567890 # 群组 ID(负数)
常见错误:使用 "*" 作为通配符允许所有用户/群组是一个严重的安全风险。审计系统会对 allowFrom.*.wildcard 配置生成告警。
这两个渠道使用更复杂的多文件认证机制,包括 JID(Jabber ID)映射和持久化会话配置文件。认证状态存储在用户级session文件中,支持多账号管理。
系统在 src/security/audit.ts(688 行代码)中实现了全面的安全审计框架,能够检测配置中的安全问题并生成不同严重级别的告警。
| 类别 | 检测项 | 严重级别 |
|---|---|---|
| Gateway | bind_no_auth(无认证绑定) |
Critical |
| Gateway | tailscale_funnel(公网暴露) |
Critical |
| Gateway | token_too_short(<24字符) |
Warn |
| Control UI | device_auth_disabled |
Critical |
| Control UI | insecure_auth(无TLS) |
Warn |
| Filesystem | perms_world_writable |
Critical |
| Filesystem | config_writable(config可写) |
Warn |
| Elevated | allowFrom.*.wildcard(通配符) |
Critical |
| Browser | remote_cdp_http(HTTP CDP) |
Warn |
src/security/external-content.ts 模块处理来自外部来源的内容(邮件、Webhook、浏览器抓取等),实现了:
这对于防范 Prompt 注入攻击尤为重要,因为恶意用户可能通过外部内容尝试操纵 AI 代理的行为。
基于代码分析,以下是系统中存在的主要安全风险:
开放的 DM 策略:默认允许任何用户通过私信与 Bot 交互。修复:设置 dmPolicy: "allowlist"。
通配符白名单:在 allowFrom 中使用 "*" 会绕过所有访问控制。修复:使用具体的用户/群组 ID。
禁用的访问组:commands.useAccessGroups: false 允许所有用户执行所有命令。修复:启用并配置访问组。
公网暴露(Tailscale Funnel):将网关暴露到公网而不增加额外认证。修复:使用 mode: "serve" 并配置强认证。
useAccessGroups: true、dmPolicy: "allowlist"🔴 开放 DM 策略允许任意用户与 Bot 交互 (critical)
默认 DM 策略允许任何用户通过私信与 Bot 交互,可能导致未授权访问和资源滥用。建议设置 dmPolicy: allowlist 并配置允许的用户列表。
🔴 useAccessGroups 默认禁用导致命令无访问控制 (critical)
Discord、Slack 等渠道的 commands.useAccessGroups 默认为 false,意味着任何能访问 Bot 的用户都可以执行所有命令,包括敏感操作。
🔴 Tailscale Funnel 模式可能暴露网关到公网 (critical)
配置 gateway.tailscale.mode=’funnel’ 会将网关暴露到公网,如果没有配置强认证,可能导致未授权访问。
⚪ 允许使用通配符白名单绕过访问控制 (high)
allowFrom 配置支持 ‘*’ 通配符,这会绕过所有访问限制。审计系统会检测此配置,但默认不阻止。
🔵 实现了时序安全的密钥比较 (info)
使用 safeEqualSecret() 进行密钥比较,有效防止时序攻击,这是安全最佳实践。
🔵 完善的速率限制机制 (info)
实现了 10次/分钟的速率限制,超限后锁定5分钟,有效防止暴力破解攻击。
export function authorizeGatewayConnect(auth: GatewayAuth, providedSecret: string): boolean {
if (!auth.secret) return true;
return safeEqualSecret(auth.secret, providedSecret);
}
const RATE_LIMIT_CONFIG = {
maxAttempts: 10,
windowMs: 60 * 1000,
lockoutMs: 5 * 60 * 1000,
cleanupIntervalMs: 60 * 1000,
};
channels:
discord:
token: ${DISCORD_BOT_TOKEN}
commands:
useAccessGroups: true
dmPolicy: "allowlist"
src/gateway/auth.tssrc/gateway/auth-rate-limit.tssrc/security/audit.tssrc/security/external-content.tssrc/security/secret-equal.tsOpenClaw 实现了一套成熟的多层输入验证和消息安全体系,用于保护多渠道消息网关免受注入攻击、恶意内容和未经授权的访问。该安全架构覆盖了从外部内容处理到 Markdown 渲染的完整消息生命周期,为处理来自 WhatsApp、Telegram、Discord、Slack、Signal 和 iMessage 等多种平台的消息提供了统一的安全保障。
外部内容保护是消息安全的第一道防线,主要实现位于 src/security/external-content.ts。该模块针对 AI 系统特有的提示词注入(Prompt Injection)攻击提供了专门的防护能力。
系统采用 12 个精心设计的正则表达式模式来检测潜在的提示词注入尝试:
// src/security/external-content.ts 中的注入检测模式示例
const INJECTION_PATTERNS = [
/ignore\s+all/i, // 尝试让 AI 忽略系统指令
/rm\s+-rf/i, // 危险的 shell 命令
/<\/?system>/i, // 尝试注入系统级标签
/\[\[SYSTEM\]\]/i, // 方括号系统标记
/###\s*INSTRUCTIONS/i, // 尝试注入新的指令块
/you\s+are\s+now/i, // 角色重置尝试
/forget\s+everything/i, // 记忆清除尝试
/new\s+persona/i, // 身份切换尝试
// ... 更多模式
];
这些模式覆盖了常见的提示词注入技术,包括指令覆盖、角色重置和系统标签注入。模式设计采用了大小写不敏感匹配,确保无法通过简单的大小写变换绕过检测。
为了让 AI 模型能够明确区分可信的系统指令和不可信的用户输入,系统使用 XML 风格的边界标记包裹外部内容:
// 外部内容包装示例
function wrapExternalContent(content: string, source: ExternalSource): string {
const sanitized = sanitizeExternalContent(content);
return `<<<EXTERNAL_UNTRUSTED_CONTENT source="${source}">>>
${sanitized}
<<<END_EXTERNAL_CONTENT>>>`;
}
// 支持的外部来源类型
type ExternalSource = 'email' | 'webhook' | 'api' | 'browser';
这种边界标记策略使得 AI 模型在处理消息时能够识别哪些内容来自外部,从而在生成响应时保持适当的谨慎。
攻击者常常使用视觉上相似的 Unicode 字符来绕过简单的文本过滤器。系统实现了同形字折叠(Homoglyph Folding)来防御此类攻击:
// Unicode 同形字规范化示例
const HOMOGLYPH_MAP: Record<string, string> = {
'<': '<', // 全角小于号 → ASCII 小于号
'>': '>', // 全角大于号 → ASCII 大于号
'{': '{', // 全角左花括号
'}': '}', // 全角右花括号
'a': 'a', // 全角字母
'A': 'A',
// ... 更多映射
};
function normalizeHomoglyphs(text: string): string {
return text.replace(/[<>{}a-zA-Z]/g,
char => HOMOGLYPH_MAP[char] || char);
}
这确保了即使攻击者使用 <system>(全角字符)而非 <system>(ASCII 字符),注入检测仍然能够正确识别。
在多渠道环境中,验证消息发送者的身份至关重要。系统在 src/channels/sender-identity.ts 中实现了多层身份验证机制。
对于 WhatsApp 和 Signal 等基于电话号码的渠道,系统使用 E.164 格式验证:
// E.164 电话号码验证
const E164_PATTERN = /^\+[1-9]\d{1,14}$/;
function validatePhoneNumber(phone: string): ValidationResult {
if (!E164_PATTERN.test(phone)) {
return {
valid: false,
error: 'Invalid E.164 phone number format'
};
}
return { valid: true };
}
// 用户名验证(禁止 @ 和空白字符)
const USERNAME_PATTERN = /^[^@\s]+$/;
function validateUsername(username: string): ValidationResult {
if (!username || username.trim() === '') {
return { valid: false, error: 'Empty user ID detected' };
}
if (!USERNAME_PATTERN.test(username)) {
return { valid: false, error: 'Username contains invalid characters' };
}
return { valid: true };
}
对于群组消息,系统强制要求至少存在一个有效的成员 ID:
function validateGroupContext(context: GroupContext): ValidationResult {
if (!context.memberIds || context.memberIds.length === 0) {
return {
valid: false,
error: 'Group context requires at least one member ID'
};
}
// 验证每个成员 ID
for (const id of context.memberIds) {
if (!id || id.trim() === '') {
return { valid: false, error: 'Empty member ID in group' };
}
}
return { valid: true };
}
这种验证确保了消息来源的可追溯性,是后续授权决策的基础。
入站消息处理位于 src/auto-reply/reply/inbound-context.ts 和 src/auto-reply/reply/inbound-text.ts,负责规范化和验证传入的消息内容。
系统对所有入站文本执行统一的规范化处理:
function normalizeInboundText(text: string): string {
return text
// 统一换行符
.replace(/\r\n/g, '\n')
.replace(/\r/g, '\n')
// 移除控制字符(保留常见的空白字符)
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '')
// 规范化转义序列
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t');
}
系统采用优先级策略来解析消息体内容:
这种分层策略确保了命令消息获得正确处理,同时为普通消息提供适当的转换。
入站上下文采用”默认拒绝”(Default-Deny)的授权策略。每条消息必须通过显式的授权检查才能被处理:
interface InboundContext {
// 60+ 字段用于完整的消息上下文
sender: SenderIdentity;
channel: ChannelInfo;
permissions: PermissionSet;
// 默认为 false,必须显式授权
authorized: boolean;
authorizationReason?: string;
}
为了防止 XSS(跨站脚本)攻击,UI 层的 Markdown 渲染实现了严格的安全控制,位于 ui/src/ui/markdown.ts。
系统使用 DOMPurify 库进行 HTML 净化,配置仅允许 27 个安全的 HTML 标签:
import DOMPurify from 'dompurify';
const ALLOWED_TAGS = [
'p', 'br', 'strong', 'em', 'code', 'pre',
'ul', 'ol', 'li', 'blockquote', 'a', 'img',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
'span', 'div', 'hr'
];
const ALLOWED_ATTRS = {
'a': ['href', 'title', 'rel', 'target'],
'img': ['src', 'alt', 'title', 'width', 'height'],
'code': ['class'], // 用于语法高亮
'*': ['class'] // 通用 class 属性
};
function sanitizeMarkdown(html: string): string {
return DOMPurify.sanitize(html, {
ALLOWED_TAGS,
ALLOWED_ATTR: Object.values(ALLOWED_ATTRS).flat(),
// 强制链接安全属性
ADD_ATTR: ['rel'],
FORCE_BODY: true
});
}
所有外部链接都被强制添加安全属性:
function secureLinks(html: string): string {
const doc = new DOMParser().parseFromString(html, 'text/html');
doc.querySelectorAll('a[href]').forEach(link => {
const href = link.getAttribute('href');
// 检查是否为外部链接
if (href && !href.startsWith('/') && !href.startsWith('#')) {
link.setAttribute('rel', 'noreferrer noopener');
link.setAttribute('target', '_blank');
}
});
return doc.body.innerHTML;
}
rel="noreferrer noopener" 属性组合防止了目标页面获取来源页面的引用,这对于防止某些类型的钓鱼攻击和信息泄露至关重要。
为防止 DoS 攻击,Markdown 处理实施了严格的限制:
| 限制类型 | 值 | 目的 |
|---|---|---|
| 最大字符数 | 140,000 | 防止内存耗尽 |
| 缓存条目数 | 200 | 限制缓存消耗 |
| 最大嵌套深度 | 10 | 防止渲染栈溢出 |
不同消息渠道有各自独特的安全特性,系统在 src/channels/command-gating.ts 和 src/channels/allowlist-match.ts 中实现了统一的安全控制层。
命令门控确保敏感命令只能由授权用户执行:
interface CommandGatingConfig {
// 需要额外授权的命令列表
elevatedCommands: string[];
// 允许执行提升命令的用户/群组
allowFrom: AllowlistEntry[];
// 是否启用访问组
useAccessGroups: boolean;
}
function isCommandAllowed(
command: string,
sender: SenderIdentity,
config: CommandGatingConfig
): boolean {
// 非提升命令默认允许
if (!config.elevatedCommands.includes(command)) {
return true;
}
// 检查发送者是否在允许列表中
return matchesAllowlist(sender, config.allowFrom);
}
Telegram 渠道实现了额外的群组和话题验证:
function validateTelegramContext(context: TelegramContext): ValidationResult {
// 验证 Telegram ID 为数字格式
if (!/^-?\d+$/.test(context.chatId)) {
return { valid: false, error: 'Invalid Telegram chat ID' };
}
// 群组消息需要验证话题 ID(如果适用)
if (context.isGroup && context.topicId) {
if (!/^\d+$/.test(context.topicId)) {
return { valid: false, error: 'Invalid topic ID' };
}
}
return { valid: true };
}
允许列表验证支持多种匹配模式:
type AllowlistEntry =
| { type: 'user'; id: string } // 单个用户
| { type: 'group'; id: string } // 群组
| { type: 'pattern'; regex: string } // 正则匹配
| { type: 'accessGroup'; name: string }; // 访问组
function matchesAllowlist(
sender: SenderIdentity,
allowlist: AllowlistEntry[]
): boolean {
return allowlist.some(entry => {
switch (entry.type) {
case 'user':
return sender.userId === entry.id;
case 'group':
return sender.groupId === entry.id;
case 'pattern':
return new RegExp(entry.regex).test(sender.userId);
case 'accessGroup':
return sender.accessGroups?.includes(entry.name);
default:
return false;
}
});
}
当系统需要获取外部 Web 内容时,实施了多项安全措施来防止 SSRF(服务器端请求伪造)攻击:
10.x.x.x、192.168.x.x、127.0.0.1)| 严重程度 | 发现 | 建议 |
|---|---|---|
| 🟢 信息 | 提示词注入检测覆盖全面 | 定期更新检测模式以应对新型攻击 |
| 🟢 信息 | DOMPurify 集成完善 | 保持库版本更新 |
| 🟡 中等 | 正则模式可能存在 ReDoS 风险 | 考虑使用 RE2 替代 |
| 🟡 中等 | 允许列表配置易出错 | 添加配置验证和警告 |
MsgContext 提供完整的审计追踪能力🔵 全面的提示词注入防护 (info)
系统实现了 12 个正则表达式模式检测提示词注入,配合 Unicode 同形字折叠和内容边界标记,形成多层防护
🔵 DOMPurify XSS 防护 (info)
UI 层使用 DOMPurify 进行 HTML 净化,仅允许 27 个安全标签,并强制添加链接安全属性
⚪ 正则表达式 ReDoS 风险 (low)
部分注入检测正则可能存在 ReDoS(正则表达式拒绝服务)风险,建议评估使用 RE2 引擎
🔵 统一的多渠道安全抽象 (info)
命令门控和允许列表机制为所有渠道提供一致的安全策略,简化了安全管理
const INJECTION_PATTERNS = [
/ignore\s+all/i,
/rm\s+-rf/i,
/<\/?system>/i,
/###\s*INSTRUCTIONS/i
];
DOMPurify.sanitize(html, {
ALLOWED_TAGS,
ALLOWED_ATTR: Object.values(ALLOWED_ATTRS).flat(),
ADD_ATTR: ['rel'],
FORCE_BODY: true
});
function matchesAllowlist(sender, allowlist) {
return allowlist.some(entry => {
switch (entry.type) {
case 'user': return sender.userId === entry.id;
case 'group': return sender.groupId === entry.id;
case 'accessGroup': return sender.accessGroups?.includes(entry.name);
}
});
}
src/security/external-content.tssrc/channels/sender-identity.tssrc/auto-reply/reply/inbound-context.tssrc/auto-reply/reply/inbound-text.tsui/src/ui/markdown.tssrc/channels/command-gating.tssrc/channels/allowlist-match.tsOpenClaw 实现了一套多层次的密钥管理和配置安全体系,涵盖从环境变量注入、敏感数据保护到文件系统权限控制的完整安全链条。这套体系的设计理念是「默认安全」——即使开发者忘记配置某些安全选项,系统也会通过审计警告和合理的默认值来降低风险。
系统将密钥分为四大类,每类采用不同的管理策略:
网关认证密钥 是最核心的安全凭证,包括 OPENCLAW_GATEWAY_TOKEN 和 OPENCLAW_GATEWAY_PASSWORD。这两个密钥控制着整个网关的访问权限,系统强制要求通过环境变量注入,如果在配置文件中发现明文密钥,审计系统会立即发出警告。网关认证支持两种模式:Token 模式适用于程序化访问,Password 模式适用于简化的终端操作。两者互斥,同时配置时 Token 优先。
平台机器人令牌 涵盖所有第三方平台的接入凭证:DISCORD_BOT_TOKEN、TELEGRAM_BOT_TOKEN、SLACK_BOT_TOKEN 等。这些令牌的特殊之处在于,每个平台都有不同的令牌格式和轮换策略。例如,Telegram 令牌格式为 数字:字母数字串,而 Slack 令牌以 xox[baprs]- 前缀开头。系统的日志脱敏模块能自动识别这些格式并进行遮蔽处理。
API 密钥 采用统一的命名规范 ${PROVIDER}_API_KEY,支持 OpenAI、Anthropic、Google 等主流 AI 提供商。每个 Agent 可以配置独立的 OAuth 密钥,实现细粒度的访问控制。
Webhook 令牌 用于外部系统回调验证,存储在配置的 hooks.token 字段。审计系统会检查令牌强度:长度小于 24 字符或在多个 Webhook 间复用都会触发警告。
环境变量注入由 src/config/env-substitution.ts 模块实现,采用 Shell 风格的语法:
// 基本语法:${VAR_NAME} 从环境变量读取值
// 转义语法:$${VAR} 输出字面量 ${VAR}
// 错误处理:变量不存在时抛出 MissingEnvVarError
const config = {
gateway: {
auth: {
token: "${OPENCLAW_GATEWAY_TOKEN}" // 从环境变量注入
}
},
discord: {
botToken: "${DISCORD_BOT_TOKEN}" // 平台令牌
}
};
这种设计的优势在于:配置文件可以安全地提交到版本控制系统,真正的密钥值只存在于运行时环境中。配合 Docker Secrets、Kubernetes Secrets 或云平台的密钥管理服务,可以实现零明文存储。
系统使用 src/security/secret-equal.ts 中的 safeEqualSecret 函数进行所有密钥验证,底层调用 Node.js 的 timingSafeEqual 方法。这可以防止时序攻击——攻击者无法通过测量响应时间来逐字符猜测密钥:
import { timingSafeEqual } from 'crypto';
export function safeEqualSecret(a: string, b: string): boolean {
// 转换为 Buffer 进行恒定时间比较
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
// 长度不同时仍执行完整比较,避免泄露信息
if (bufA.length !== bufB.length) {
// 比较 bufA 与自身,保持恒定时间
timingSafeEqual(bufA, bufA);
return false;
}
return timingSafeEqual(bufA, bufB);
}
日志脱敏由 src/logging/redact.ts 模块实现(150 行代码),支持 15 种以上的敏感数据模式识别:
| 模式类型 | 匹配规则 | 示例 |
|---|---|---|
| 环境变量 | KEY\|TOKEN\|SECRET\|PASSWORD = value |
API_KEY = sk-xxx |
| JSON 字段 | "apiKey"\|"token"\|"secret"\|"password" |
{"apiKey": "xxx"} |
| CLI 参数 | --api-key\|--token\|--secret\|--password |
--api-key sk-xxx |
| Auth 头 | Authorization: Bearer ... |
Bearer eyJxxx |
| PEM 密钥 | -----BEGIN...PRIVATE KEY----- |
RSA 私钥 |
| 平台前缀 | sk-*, ghp_*, xox[baprs]-*, gsk_*, AIza* |
各平台 API 密钥 |
脱敏策略根据数据长度智能调整:
***sk-abc...xyz9…redacted…系统对配置文件和状态目录实施严格的权限要求:
| 资源类型 | 推荐权限 | 说明 |
|---|---|---|
| 状态目录 | 0o700 |
仅所有者可访问 |
| 配置文件 | 0o600 |
仅所有者可读写 |
| 媒体文件 | 0o600 |
创建时自动设置 |
| 媒体目录 | 0o700 |
创建时自动设置 |
审计系统会检测权限异常并报告不同级别的问题:
perms_world_writable(配置文件可被任意用户写入)、perms_world_readable(配置文件可被任意用户读取)、perms_state_writable(状态目录可被任意用户写入)perms_group_readable(配置文件可被同组用户读取)// 媒体存储模块自动设置安全权限
await fs.mkdir(mediaDir, { recursive: true, mode: 0o700 });
await fs.writeFile(dest, buffer, { mode: 0o600 });
配置系统实现了原型污染防护机制(src/config/config-paths.ts,84 行代码),阻止攻击者通过配置路径操纵 JavaScript 原型链:
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
function parseConfigPath(raw: string) {
const parts = trimmed.split(".");
// 检测并阻止原型污染攻击
if (parts.some((part) => BLOCKED_KEYS.has(part))) {
return { ok: false, error: "Invalid path segment." };
}
return { ok: true, path: parts };
}
这种防护对于接受用户输入配置路径的场景至关重要。例如,如果用户尝试访问 __proto__.polluted,系统会直接拒绝,而不是无意中修改所有对象的原型。
系统支持通过环境变量覆盖默认路径,实现灵活的部署配置:
OPENCLAW_STATE_DIR:覆盖状态目录位置OPENCLAW_CONFIG_PATH:覆盖配置文件路径OPENCLAW_GATEWAY_PORT:覆盖网关端口XDG_* 系列变量:遵循 XDG 基础目录规范测试环境实现了完全的环境隔离:
HOME、USERPROFILE、XDG_*、OPENCLAW_*OPENCLAW_STATE_DIR审计系统(src/security/audit-extra.sync.ts)会主动检测配置中的安全隐患:
logging.redactSensitive = "off" 时警告基于以上分析,推荐采用以下配置安全实践:
600,状态目录权限为 700"tools" 模式)🔵 完善的环境变量替换机制 (info)
系统通过 ${VAR_NAME} 语法支持配置文件中的环境变量注入,避免明文密钥存储
🔵 时序安全的密钥比对 (info)
使用 timingSafeEqual 防止时序攻击,所有密钥验证都经过恒定时间比较
🔵 全面的日志脱敏系统 (info)
支持 15+ 种敏感数据模式识别,自动遮蔽 API 密钥、令牌、密码等
🔵 原型污染防护 (info)
配置路径解析阻止 proto、prototype、constructor 等危险路径
⚪ 文件权限审计 (low)
系统检测配置文件和状态目录的权限异常,但需要管理员手动修复
import { timingSafeEqual } from 'crypto';
export function safeEqualSecret(a: string, b: string): boolean {
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
if (bufA.length !== bufB.length) {
timingSafeEqual(bufA, bufA);
return false;
}
return timingSafeEqual(bufA, bufB);
}
const BLOCKED_KEYS = new Set(["__proto__", "prototype", "constructor"]);
function parseConfigPath(raw: string) {
const parts = trimmed.split(".");
if (parts.some((part) => BLOCKED_KEYS.has(part))) {
return { ok: false, error: "Invalid path segment." };
}
return { ok: true, path: parts };
}
src/config/env-substitution.tssrc/security/secret-equal.tssrc/logging/redact.tssrc/config/config-paths.tssrc/security/audit.tssrc/security/audit-extra.sync.tssrc/media/store.tsOpenClaw 采用了一种独特的「无数据库」存储架构,完全依赖文件系统进行数据持久化。这种设计决策与传统的 Redis/PostgreSQL/MongoDB 方案形成鲜明对比,体现了对部署简易性和运维成本的深度考量。系统通过 JSON 文件、原子写入和文件锁定机制,在保证数据一致性的同时,实现了零外部依赖的轻量级部署模式。
系统的核心存储理念是「文件即数据库」。所有持久化状态都存储在用户主目录下的 ~/.openclaw/ 结构中,主要包含四个关键目录:
| 目录 | 用途 | 环境变量覆盖 |
|---|---|---|
~/.openclaw/state/ |
运行时状态 | OPENCLAW_STATE_DIR |
~/.openclaw/state/oauth/ |
OAuth 凭证 | - |
~/.openclaw/ |
配置文件 | - |
~/.openclaw/media/ |
临时媒体文件 | - |
这种设计带来了显著的优势:无需安装数据库服务、配置文件可以直接版本控制、备份只需复制目录、跨平台兼容性极佳。但同时也意味着系统不适合高并发写入场景,这是架构层面的有意权衡。
Session Store(893 LOC,位于 src/config/sessions/store.ts)是整个存储系统的核心组件,负责管理 Agent 会话状态、消息路由和使用量追踪。
const SESSION_STORE_CACHE = new Map<string, SessionStoreCacheEntry>();
const DEFAULT_SESSION_STORE_TTL_MS = 45_000; // 45秒缓存
缓存系统采用文件 mtime 验证策略:每次读取时比较缓存时间戳与文件修改时间,确保数据新鲜度。值得注意的是,系统使用深度克隆(deep cloning)防止外部代码意外修改缓存数据,这是一个防御性编程的典型实践。
每个会话条目包含丰富的元数据:
sessionId、updatedAt 时间戳deliveryContext 包含 channel、to、accountId、threadIdmodelProvider、model、contextTokensinputTokens、outputTokens、totalTokenscompactionCount、abortedLastRunconst LOCK_QUEUES = new Map<string, SessionStoreLockQueue>();
async function withSessionStoreLock<T>(
storePath: string,
fn: () => Promise<T>
): Promise<T> {
// 每文件独立锁队列,默认10秒超时
// 30秒检测过期锁,防止死锁
}
系统实现了细粒度的文件级锁定:每个存储文件拥有独立的锁队列,支持 10 秒超时和 30 秒过期锁检测。这种设计在单进程环境下提供了可靠的并发保护,但需要注意的是,跨进程锁定依赖操作系统的文件锁机制。
| 维护操作 | 默认阈值 | 说明 |
|---|---|---|
| Pruning | 30天 | 删除超时条目 |
| Capping | 500条 | 限制最大条目数 |
| Rotation | 10MB | 轮转大文件 |
维护模式支持 warn(仅告警)和 enforce(自动清理)两种策略,可根据运维需求灵活配置。
认证系统采用分层存储架构,将不同类型的凭证隔离管理。
type AuthProfileStore = {
version: 2;
profiles: Record<string, AuthProfileCredential>;
order?: Record<string, string[]>; // 轮换优先级
lastGood?: Record<string, string>; // 最后成功的配置
usageStats?: Record<string, ProfileUsageStats>; // 使用统计
}
支持三种凭证类型:api_key(API 密钥)、oauth(含 access/refresh token 和过期时间)、token(简单 Bearer token)。
智能轮换机制:系统维护 order 字段定义轮换优先级,lastGood 记录最后成功的配置,实现自动故障转移。当 API 配额耗尽时,系统自动切换到下一个可用配置。
type DeviceAuthStore = {
version: 1;
deviceId: string;
tokens: Record<string, DeviceAuthEntry>;
}
设备认证存储实现了基于角色(operator、node)和作用域的授权模型,确保不同设备拥有恰当的权限边界。
系统实现了多个独立的内存缓存,针对不同场景优化:
| 缓存名称 | 位置 | TTL | 用途 |
|---|---|---|---|
| Session Store Cache | sessions/store.ts |
45秒 | 会话数据 |
| Sent Message Cache | telegram/sent-message-cache.ts |
24小时 | 消息去重 |
| Sticker Cache | telegram/sticker-cache.ts |
- | 表情包文件 |
| Presence Cache | discord/monitor/presence-cache.ts |
- | 在线状态 |
缓存工具模块(src/config/cache-utils.ts)提供统一的 TTL 解析、启用检测和文件修改时间获取功能,支持通过环境变量(如 OPENCLAW_SESSION_CACHE_TTL_MS)动态调整缓存策略。
Media Store(263 LOC,src/media/store.ts)负责处理下载和上传的媒体文件,实现了严格的安全控制。
export const MEDIA_MAX_BYTES = 5 * 1024 * 1024; // 5MB 限制
const DEFAULT_TTL_MS = 2 * 60 * 1000; // 2分钟 TTL
关键安全特性:
resolvePinnedHostname() 验证下载 URL,防止服务端请求伪造攻击对于需要语义搜索能力的场景,系统提供了可选的 SQLite 数据库支持(src/memory/memory-schema.ts):
-- 文件索引表
CREATE TABLE files (
path TEXT PRIMARY KEY,
source TEXT NOT NULL DEFAULT 'memory',
hash TEXT NOT NULL,
mtime INTEGER NOT NULL,
size INTEGER NOT NULL
);
-- 向量嵌入表
CREATE TABLE chunks (
id TEXT PRIMARY KEY,
path TEXT NOT NULL,
embedding TEXT NOT NULL, -- 序列化的向量
model TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
系统支持两种搜索模式:基于 LanceDB 的向量相似度搜索和基于 FTS5 的全文搜索。FTS5 可用性通过运行时检测自动判断。
管理渠道配对请求和白名单,实现安全的用户授权流程:
async function saveCronStore(storePath: string, store: CronStoreFile) {
const tmp = `${storePath}.${process.pid}.${Math.random()}.tmp`;
await fs.promises.writeFile(tmp, json, "utf-8");
await fs.promises.rename(tmp, storePath); // 原子重命名
await fs.promises.copyFile(storePath, `${storePath}.bak`); // 保存备份
}
典型的原子写入实现:先写临时文件,再原子重命名,最后创建备份。支持 JSON5 格式,允许注释和尾随逗号。
管理 Baileys 库的 WhatsApp Web 会话凭证,实现了自动恢复机制:检测损坏的 creds.json,自动从 .bak 备份恢复。
所有存储模块都遵循统一的原子写入模式:
{path}.{pid}.{random}.tmpfs.rename(tmp, path) 保证原子性{path}.bak 用于灾难恢复version 字段,支持自动迁移这种设计确保了即使在写入过程中发生崩溃,数据也不会处于损坏状态——要么是旧数据,要么是完整的新数据。
优势:
局限:
最佳适用场景:单节点部署、个人/小团队使用、边缘计算环境、资源受限的嵌入式设备。
🔵 采用纯文件系统存储架构 (info)
系统完全依赖 JSON 文件和原子写入实现持久化,零外部数据库依赖,极大简化部署复杂度
🔵 多级缓存策略 (info)
Session Store、Sent Message Cache、Sticker Cache 等多个独立缓存,针对不同场景优化 TTL
⚪ 并发写入能力受限 (low)
文件锁机制在高并发场景下可能成为瓶颈,不适合多节点部署
🔵 完善的安全机制 (info)
Media Store 实现 SSRF 防护、大小限制、自动清理;Auth Store 支持凭证轮换和故障转移
const LOCK_QUEUES = new Map<string, SessionStoreLockQueue>();
async function withSessionStoreLock<T>(
storePath: string,
fn: () => Promise<T>
): Promise<T> {
// 每文件独立锁队列,默认10秒超时
// 30秒检测过期锁,防止死锁
}
async function saveCronStore(storePath: string, store: CronStoreFile) {
const tmp = `${storePath}.${process.pid}.${Math.random()}.tmp`;
await fs.promises.writeFile(tmp, json, "utf-8");
await fs.promises.rename(tmp, storePath); // 原子重命名
await fs.promises.copyFile(storePath, `${storePath}.bak`); // 保存备份
}
src/config/sessions/store.tssrc/media/store.tssrc/cron/store.tssrc/agents/auth-profiles/store.tssrc/infra/device-auth-store.tssrc/memory/memory-schema.tssrc/pairing/pairing-store.tssrc/web/auth-store.tsOpenClaw 项目采用了一套成熟且高度模块化的测试基础设施,基于 Vitest 构建了六套独立的测试配置,分别针对单元测试、集成测试、端到端测试、网关测试、扩展测试和实时服务测试等不同场景。这种分层测试架构不仅确保了代码质量,还为开发者提供了灵活的测试执行选项,使得快速迭代和全面验证可以并行进行。
OpenClaw 的测试基础设施由六套 Vitest 配置文件构成,每套配置针对特定的测试场景进行了优化。这种设计体现了”关注点分离”原则,使得开发者可以根据当前工作内容选择最合适的测试集合,而无需运行整个测试套件。
vitest.config.ts)基础配置定义了所有测试共享的默认行为,是其他配置的继承基础:
// vitest.config.ts - 核心配置
export default defineConfig({
test: {
pool: 'forks', // 进程隔离,避免内存泄漏
timeout: 120_000, // 适应复杂测试场景的超时设置
hookTimeout: 180_000, // Windows 系统钩子超时补偿
maxWorkers: process.env.CI ? 3 : 16, // CI/本地自适应
autoUnstubEnv: true, // 测试间自动清理环境变量
autoUnstubGlobals: true, // 测试间自动清理全局变量
setupFiles: ['test/setup.ts'],
coverage: {
provider: 'v8',
thresholds: {
lines: 70,
functions: 70,
statements: 70,
branches: 55
}
}
}
});
设计决策说明:
pool: 'forks'): 每个测试在独立进程中运行,防止测试间的状态污染和内存泄漏累积autoUnstubEnv 和 autoUnstubGlobals 确保测试隔离性,避免因遗忘清理导致的难以调试的测试失败| 配置文件 | 作用范围 | 隔离级别 | 并发策略 |
|---|---|---|---|
vitest.unit.config.ts |
src/**/*.test.ts (排除 gateway/extensions) |
forks | 高并发 |
vitest.gateway.config.ts |
src/gateway/**/*.test.ts |
forks | 中等并发 |
vitest.extensions.config.ts |
extensions/**/*.test.ts |
forks | 中等并发 |
vitest.e2e.config.ts |
**/*.e2e.test.ts |
vmForks (VM隔离) | 低并发 (2-4) |
vitest.live.config.ts |
**/*.live.test.ts |
forks | 串行 (1 worker) |
E2E 测试的 VM 隔离: E2E 测试使用 vmForks 池,提供比进程隔离更强的 VM 级别隔离。这对于测试网关服务器、WebSocket 连接等需要完整网络栈的场景至关重要。
Live 测试的串行执行: Live 测试访问真实外部服务(如 AI 提供商 API),串行执行避免了 API 限流和资源争用问题。
测试隔离是 OpenClaw 测试基础设施的核心特性。test/setup.ts(190 行)和 test/test-env.ts(148 行)共同构建了一个完整的测试隔离层,确保每个测试套件在”干净”的环境中运行。
// test/test-env.ts - 环境隔离核心逻辑
export function createIsolatedTestEnv() {
// 保存原始环境
const savedEnv = [
'HOME', 'XDG_CONFIG_HOME', 'XDG_STATE_HOME',
'OPENCLAW_HOME', 'OPENCLAW_STATE_DIR',
'TELEGRAM_BOT_TOKEN', 'DISCORD_BOT_TOKEN',
'SLACK_BOT_TOKEN', 'GITHUB_TOKEN'
// ... 共 25+ 个环境变量
].map(key => [key, process.env[key]]);
// 创建临时 HOME 目录
const tempHome = fs.mkdtempSync(
path.join(os.tmpdir(), 'openclaw-test-')
);
// 设置测试环境
process.env.HOME = tempHome;
process.env.XDG_CONFIG_HOME = path.join(tempHome, '.config');
process.env.XDG_STATE_HOME = path.join(tempHome, '.local/state');
process.env.OPENCLAW_TEST_FAST = '1';
// 清除敏感凭据
delete process.env.TELEGRAM_BOT_TOKEN;
delete process.env.DISCORD_BOT_TOKEN;
// ...
return {
tempHome,
cleanup: () => {
savedEnv.forEach(([key, value]) => {
if (value !== undefined) process.env[key] = value;
else delete process.env[key];
});
fs.rmSync(tempHome, { recursive: true, force: true });
}
};
}
设计亮点:
OPENCLAW_STATE_DIR 环境变量处理 Windows 路径差异每个测试都获得一个独立的插件注册表,预装了存根插件:
// test/setup.ts - 插件隔离
import { setActivePluginRegistry, createTestPluginRegistry } from '../src/test-utils/channel-plugins';
beforeEach(() => {
// 创建包含存根插件的测试注册表
const registry = createTestPluginRegistry([
createStubPlugin('discord'),
createStubPlugin('slack'),
createStubPlugin('telegram'),
createStubPlugin('whatsapp'),
createStubPlugin('signal'),
createStubPlugin('imessage')
]);
setActivePluginRegistry(registry);
});
afterEach(() => {
// 清理 fake timers
if (vi.isFakeTimers()) {
vi.useRealTimers();
}
});
项目提供了多种测试执行命令,满足不同开发场景需求:
# 开发迭代 - 快速单元测试
pnpm test:fast # 仅运行 src/ 下的单元测试,排除 gateway/extensions
# 功能验证 - 完整测试套件
pnpm test # 并行运行所有测试
# 集成测试 - 端到端场景
pnpm test:e2e # VM 隔离的端到端测试
# 覆盖率检查
pnpm test:coverage # 单元测试 + V8 覆盖率报告
# 完整验证 - CI 流程
pnpm test:all # 全部测试 + Docker 测试
Live 测试(*.live.test.ts)用于验证与真实外部服务的集成,需要特殊的执行条件:
# 启用 Live 测试
OPENCLAW_LIVE_TEST=1 pnpm test:live
Live 测试文件分布(共 10 个):
| 类别 | 文件 | 测试目标 |
|---|---|---|
| AI 模型 | google-gemini-switch.live.test.ts |
Gemini 模型切换 |
| AI 模型 | minimax.live.test.ts |
MiniMax AI 提供商 |
| AI 模型 | anthropic.setup-token.live.test.ts |
Anthropic Token 配置 |
| AI 模型 | models.profiles.live.test.ts |
模型配置解析 |
| AI 模型 | pi-embedded-runner-extraparams.live.test.ts |
Pi Agent 参数 |
| AI 模型 | zai.live.test.ts |
ZAI 提供商 |
| 网关 | gateway-cli-backend.live.test.ts |
CLI 后端集成 |
| 网关 | gateway-models.profiles.live.test.ts |
网关模型配置 |
| 媒体 | deepgram/audio.live.test.ts |
Deepgram 音频转录 |
| 浏览器 | pw-session.browserless.live.test.ts |
Browserless Playwright |
Live 测试环境差异:
// 常规测试 vs Live 测试环境
if (process.env.OPENCLAW_LIVE_TEST === '1') {
// Live 模式:使用真实用户环境
loadProfileEnv(); // 加载 ~/.profile 环境变量
// 不创建临时 HOME,使用真实配置和凭据
} else {
// 常规模式:完全隔离
return createIsolatedTestEnv();
}
OpenClaw 的测试代码展示了多种值得借鉴的测试模式:
describe('sleep', () => {
it('resolves after delay using fake timers', async () => {
vi.useFakeTimers(); // 启用 fake timers
const promise = sleep(1000);
vi.advanceTimersByTime(1000); // 推进时间
await expect(promise).resolves.toBeUndefined();
vi.useRealTimers(); // 恢复真实 timers
});
});
价值:避免测试中的真实等待,将秒级测试压缩到毫秒级。
it('prefers OPENCLAW_HOME over HOME', () => {
vi.stubEnv('OPENCLAW_HOME', '/srv/openclaw-home');
vi.stubEnv('HOME', '/home/other');
expect(resolveHomeDir()).toBe(path.resolve('/srv/openclaw-home'));
vi.unstubAllEnvs(); // 清理(setup.ts 的 autoUnstubEnv 也会处理)
});
it('maps @lid using reverse mapping file', () => {
const spy = vi.spyOn(fs, 'readFileSync').mockImplementation((...args) => {
if (args[0] === mappingPath) return '"5551234"';
return originalReadFileSync(...args);
});
expect(jidToE164('123@lid')).toBe('+5551234');
spy.mockRestore();
});
// 解决并行测试端口冲突
export async function getDeterministicFreePortBlock(
params?: { offsets?: number[] }
): Promise<number> {
// 每个 worker 分配独立端口范围 (30000-59999)
// 按块扫描避免派生端口重叠
// 最后回退到 OS 分配端口
}
项目采用了务实的覆盖率策略,平衡了代码质量保证与测试维护成本:
覆盖率阈值:
明确排除的模块:
cli/ - CLI 交互通过 E2E 测试验证channels/ - 渠道插件通过集成测试验证agents/ - Agent 运行时通过 E2E 和 Live 测试验证gateway/ - 网关有专用测试配置tui/ - 终端 UI 通过手动测试wizard/ - 向导流程通过手动测试设计哲学:OpenClaw 认识到不同类型的代码需要不同的测试策略。纯函数和工具类追求高覆盖率,而与外部系统交互的 IO 密集型代码则依赖集成测试和 E2E 测试。
当前优势:
改进建议:
extensions/ 目录当前无测试文件,建议添加插件开发示例测试@slow、@network 等标签,支持更细粒度的测试选择🔵 成熟的多配置 Vitest 架构 (info)
项目使用六套独立的 Vitest 配置,分别针对单元测试、网关测试、扩展测试、E2E 测试和 Live 测试,体现了良好的关注点分离
🔵 强测试隔离机制 (info)
通过临时 HOME 目录、25+ 环境变量保存/恢复、插件注册表重置实现完整的测试隔离
⚪ 扩展模块测试空缺 (low)
extensions/ 目录当前没有测试文件,建议添加插件开发示例测试以提供开发指导
🔵 务实的覆盖率策略 (info)
70% 行/函数/语句覆盖率 + 55% 分支覆盖率,明确排除 CLI/TUI 等需要手动验证的模块
export default defineConfig({
test: {
pool: 'forks',
timeout: 120_000,
maxWorkers: process.env.CI ? 3 : 16,
autoUnstubEnv: true,
coverage: { provider: 'v8', thresholds: { lines: 70, branches: 55 } }
}
});
export function createIsolatedTestEnv() {
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), 'openclaw-test-'));
process.env.HOME = tempHome;
delete process.env.TELEGRAM_BOT_TOKEN;
return { tempHome, cleanup: () => fs.rmSync(tempHome, { recursive: true }) };
}
it('resolves after delay', async () => {
vi.useFakeTimers();
const promise = sleep(1000);
vi.advanceTimersByTime(1000);
await expect(promise).resolves.toBeUndefined();
vi.useRealTimers();
});
vitest.config.tsvitest.unit.config.tsvitest.e2e.config.tsvitest.live.config.tstest/setup.tstest/test-env.tssrc/test-utils/ports.tssrc/test-utils/channel-plugins.ts展示 OpenClaw E2E 测试框架的分层架构,包括测试运行器、模拟层、隔离机制和被测系统之间的关系
Complexity: 15 nodes, 18 edges
Related files:
🔵 成熟的 E2E 测试架构 (info)
使用 Vitest vmForks 池模式实现进程级隔离,共 906 行测试代码覆盖 3 个核心场景
🔵 完善的外部服务模拟 (info)
中央插件注册表模拟 6 个消息平台,fetch 拦截实现网络层控制
🔵 严格的环境隔离 (info)
25+ 环境变量保存/恢复,临时 HOME 目录,支持 Live 模式旁路
⚪ 缺少测试数据工厂 (low)
当前使用内联配置,可考虑引入 factory 模式提高测试数据复用性
⚪ 大型测试文件可拆分 (low)
多实例网关测试 429 LOC,可拆分为更小的聚焦测试提高可维护性
const mockPlatforms = ['discord', 'slack', 'telegram', 'whatsapp', 'signal', 'imessage'];
mockPlatforms.forEach(platform => {
vi.mock(`../src/channels/${platform}/plugin.ts`, () => ({
default: { id: platform, messaging: { sendMessage: vi.fn() } }
}));
});
beforeEach(() => {
originalHome = process.env.HOME!;
const tempHome = mkdtempSync('/tmp/openclaw-home-');
process.env.HOME = tempHome;
});
afterEach(() => {
process.env.HOME = originalHome;
});
function allocateTestPort(testIndex: number, instanceIndex: number): number {
const basePort = 18000;
const portRange = 100;
return basePort + (testIndex * portRange) + instanceIndex;
}
test/setup.tsvitest.e2e.config.tstest/*.e2e.test.tsOpenClaw 项目采用了现代化的文档架构,基于 Mintlify 文档平台构建,实现了开发者友好的多语言文档体系。整个文档系统包含约 800 个文件,覆盖了从快速入门到高级配置的完整知识体系,同时通过 AI 驱动的翻译系统实现了高效的国际化支持。
文档系统的主体位于 docs/ 目录,包含 300 多个 Markdown 文件,按照功能领域组织成清晰的层级结构:
| 目录 | 内容 | 文件数 |
|---|---|---|
docs/ |
主文档目录 | 300+ |
skills/*/SKILL.md |
技能定义文档 | 50+ |
extensions/*/README.md |
扩展说明文档 | 多个 |
src/hooks/bundled/*/HOOK.md |
内置钩子文档 | 多个 |
apps/*/README.md |
应用程序文档 | 多个 |
导航系统采用 10 个主要标签页组织内容:快速开始 (Get Started)、安装 (Install)、消息渠道 (Channels)、代理 (Agents)、工具 (Tools)、模型 (Models)、平台 (Platforms)、网关与运维 (Gateway)、参考 (Reference) 和 帮助 (Help)。这种组织方式让用户能够快速定位所需信息,无论是新手入门还是高级配置。
文档配置通过 docs/docs.json 文件管理,支持 Mintlify 的完整功能集:
{
"name": "OpenClaw",
"navigation": {
"languages": [
{
"language": "en",
"tabs": [/* 10 个完整标签页 */]
},
{
"language": "zh-Hans",
"tabs": [/* 10 个中文标签页 */]
},
{
"language": "ja",
"tabs": [/* 1 个部分标签页 */]
}
]
},
"redirects": [/* 150+ 重定向规则 */]
}
文档系统充分利用了 Mintlify 的 MDX 组件能力,包括 <Tabs>、<Note>、<Card>、<CardGroup>、<Steps>、<AccordionGroup> 等交互式组件,以及 Mermaid 图表支持,大大提升了文档的可读性和交互性。
OpenClaw 实现了一套基于 AI 的智能翻译系统,当前支持三种语言:
| 语言 | 代码 | 覆盖率 | 状态 |
|---|---|---|---|
| 英语 | en | 100% | 主要语言 |
| 简体中文 | zh-Hans/zh-CN | ~95% | 完整支持 |
| 日语 | ja | ~5% | 部分支持 |
目录结构清晰地反映了多语言组织方式:
docs/
├── index.md # 英文(主语言)
├── zh-CN/ # 中文翻译
│ ├── index.md
│ ├── start/
│ ├── channels/
│ ├── concepts/
│ └── ...(完整镜像结构)
├── ja-JP/ # 日文翻译
│ ├── index.md
│ └── start/
└── .i18n/ # 国际化资源
├── glossary.<lang>.json
└── <lang>.tm.jsonl
翻译系统采用 Claude Opus 4.5 模型,通过 OpenClaw 自身的 Pi 代理执行自动化翻译。这种「以己之力」的方式不仅验证了 OpenClaw 的能力,也确保了翻译质量的一致性。
翻译元数据通过 x-i18n frontmatter 追踪:
x-i18n:
generated_at: "2026-02-04T17:53:40Z"
model: claude-opus-4-5
provider: pi
source_hash: fc8babf7885ef91d526795051376d... # 源文件哈希
source_path: index.md
workflow: 15
源文件哈希追踪是系统的关键特性:当源文件变更时,系统能够自动检测并触发重新翻译,确保多语言版本始终同步。
系统维护两类翻译辅助资源:
翻译记忆 (Translation Memory):存储在 docs/.i18n/<lang>.tm.jsonl 文件中,采用 JSONL 格式,通过工作流、模型和文本哈希作为键值,缓存已完成的翻译以避免重复 API 调用。
术语表 (Glossary):存储在 docs/.i18n/glossary.<lang>.json 中,确保技术术语翻译的一致性:
{
"source": "troubleshooting",
"target": "故障排除",
"ignore_case": true,
"whole_word": false
}
翻译过程智能保留代码块、Mermaid 图表、MDX 组件语法、链接和 URL 等技术元素,仅翻译人类可读的文本内容。
OpenClaw 支持 15+ 模型提供商,每个提供商都有标准化的文档结构,位于 docs/providers/ 目录:
| 提供商 | 文件 | 特点 |
|---|---|---|
| Anthropic | anthropic.md | Claude 模型、API Key + Setup Token 认证 |
| OpenAI | openai.md | GPT 模型、API Key 认证 |
| Bedrock | bedrock.md | AWS Bedrock 集成 |
| LiteLLM | litellm.md | LLM 代理网关 |
| Moonshot | moonshot.md | 中国 AI 提供商 |
| MiniMax | minimax.md | 中国 AI 提供商 |
| GitHub Copilot | github-copilot.md | Copilot 集成 |
每个 Provider 文档遵循统一模板:
---
summary: "通过 {Provider} 使用..."
read_when:
- 想要使用 {Provider} 模型
- 想要 {特定功能}
title: "{Provider}"
---
# {Provider} (模型系列)
## 选项 A: API Key
### CLI 设置
### 配置示例
## 选项 B: 替代认证(如适用)
## {功能特定章节}
- 提示缓存
- 模型选择
## 故障排除
以 Anthropic 为例,文档详细介绍了两种认证方式、提示缓存配置(cacheRetention: "none" | "short" | "long"),以及完整的配置示例。
OpenClaw 支持 20+ 消息渠道,文档位于 docs/channels/,覆盖从生产级平台到社区插件的完整集成:
| 渠道 | 状态 | 说明 |
|---|---|---|
| 生产就绪 | Baileys 协议 | |
| Telegram | 完整支持 | Bot API |
| Discord | 完整支持 | Bot + Slash Commands |
| Slack | 完整支持 | Slack API |
| Signal | 支持 | Signal 协议 |
| MS Teams | 支持 | Microsoft Graph |
| Matrix | 支持 | 开放协议 |
| Feishu | 支持 | 飞书集成 |
| IRC | 支持 | 经典协议 |
Channel 文档采用更详细的标准结构:
# {Channel} ({类型})
状态: {生产就绪|支持|插件}
<CardGroup cols={3}>
<Card title="配对" href="/channels/pairing" />
<Card title="故障排除" href="/channels/troubleshooting" />
<Card title="网关配置" href="/gateway/configuration" />
</CardGroup>
## 快速设置
<Steps>...</Steps>
## 部署模式
## 运行时模型
## 访问控制与激活
## 消息规范化
## 投递、分块、媒体
## 多账户与凭证
## 故障排除
以 WhatsApp 为例,文档深入介绍了访问控制的五层机制:DM 策略、DM 允许列表、群组成员资格、群组发送者策略、以及提及要求。
开发者友好:Mintlify 平台提供了现代化的阅读体验,支持 AI 搜索、暗色模式、响应式布局。
结构一致性:Provider 和 Channel 文档遵循严格的模板,降低学习曲线,提升可预测性。
自动化翻译:AI 驱动的翻译系统大幅降低了多语言维护成本,同时保证了翻译质量。
技术准确性:通过术语表和翻译记忆,确保跨语言技术术语的一致性。
完整覆盖:从快速入门到高级 API 参考,150+ 重定向规则确保旧链接不会失效。
日语覆盖率:当前日语仅覆盖约 5%(索引 + 快速入门),建议扩展到核心概念和常用渠道。
版本化文档:随着 OpenClaw 版本迭代,可考虑引入版本化文档机制,方便用户查阅历史版本。
API 文档自动生成:当前 API 参考为手写文档,可探索从 TypeScript 类型定义自动生成 API 文档。
社区贡献指南:为提升翻译覆盖率,可建立清晰的社区贡献流程和翻译规范。
🔵 采用 Mintlify 现代化文档平台 (info)
文档系统基于 Mintlify 构建,支持 AI 搜索、MDX 组件、Mermaid 图表,提供出色的开发者体验
🔵 AI 驱动的翻译系统 (info)
使用 Claude Opus 4.5 通过 Pi 代理实现自动化翻译,配合翻译记忆和术语表确保质量
⚪ 日语覆盖率较低 (low)
日语翻译仅覆盖约 5%(索引和快速入门),建议扩展到更多核心内容
🔵 标准化文档模板 (info)
Provider 和 Channel 文档遵循严格的模板结构,确保一致性和可预测性
x-i18n:
generated_at: "2026-02-04T17:53:40Z"
model: claude-opus-4-5
provider: pi
source_hash: fc8babf7885ef91d526795051376d928599c4cf8aff75400138a0d7d9fa3b75f
source_path: index.md
workflow: 15
{
"source": "troubleshooting",
"target": "故障排除",
"ignore_case": true,
"whole_word": false
}
docs/docs.jsondocs/.i18n/README.mddocs/providers/anthropic.mddocs/channels/whatsapp.mddocs/zh-CN/OpenClaw 项目展现了卓越的技术前瞻性,其技术栈经过精心选择,几乎全部采用当前最新的稳定版本。这种技术选型策略不仅确保了项目的长期可维护性,还为未来的升级提供了充足的缓冲期。本节将从核心运行时、移动端平台、依赖管理三个维度,系统评估整个技术栈的时效性与潜在的 EOL 风险。
项目采用 Node.js 22.12.0+ 作为运行时环境,Docker 镜像基于 node:22-bookworm。Node.js 22 是 2024 年 10 月发布的 LTS(长期支持)版本,其支持周期将持续至 2027 年 4 月。这意味着项目在未来三年内无需担忧 Node.js 版本的强制升级。
TypeScript 方面,项目使用 ^5.9.3 版本,并且引入了实验性的 @typescript/native-preview 7.0.0-dev(即 tsgo,基于 Go 语言实现的 TypeScript 编译器)。编译目标设置为 ES2023,模块系统采用 NodeNext,这是目前最现代化的配置方案:
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"strict": true,
"lib": ["DOM", "DOM.Iterable", "ES2023", "ScriptHost"]
}
}
技术亮点:项目的构建工具链全面采用 Rust 生态系统,包括 tsdown ^0.20.3(基于 Rolldown 的打包器)、oxlint ^1.47.0(Rust 实现的 ESLint 替代品)、oxfmt 0.32.0(Rust 实现的格式化工具)。这种选择带来了显著的构建性能提升,同时保持了与 JavaScript 生态的完全兼容。
| 组件 | 版本 | LTS/EOL 日期 | 风险等级 |
|---|---|---|---|
| Node.js | 22.12.0+ | 2027年4月 | ✅ 低 |
| TypeScript | 5.9.3 | 持续更新 | ✅ 低 |
| pnpm | 10.23.0 | 持续更新 | ✅ 低 |
项目的 Apple 平台支持展现了激进的现代化策略。Swift 工具链版本为 6.2,源代码兼容 Swift 6.0,并启用了严格并发检查(StrictConcurrency)。这是目前 Swift 生态系统中最前沿的配置,充分利用了 Swift 6 的 actor 隔离和数据竞争安全特性。
// Package.swift 平台配置示例
let package = Package(
name: "swabble",
platforms: [
.macOS(.v15), // 最低支持 macOS 15 (2024)
.iOS(.v18) // 最低支持 iOS 18 (2024)
],
// ...
)
平台要求分析:
这种「最新主版本」策略意味着项目可以充分利用 SwiftUI 5、Swift Concurrency 等现代特性,但同时也限制了对旧设备的支持。对于面向企业或开发者的工具类应用,这是合理的权衡。
Android 端同样采用了领先的技术栈:
// build.gradle.kts 配置
android {
compileSdk = 36 // Android 15 (预览)
defaultConfig {
minSdk = 31 // Android 12 (2021)
targetSdk = 36
}
}
关键观察:minSdk = 31 (Android 12) 的设置是当前项目中需要关注的唯一潜在风险点。Android 12 设备将在 2026-2027 年间逐步退出主流支持。建议在下一个主版本迭代时将 minSdk 提升至 33 (Android 13),以获得更长的兼容周期。
核心框架依赖均采用最新主版本,展现了良好的依赖管理实践:
| 依赖包 | 版本 | 状态说明 |
|---|---|---|
| express | ^5.2.1 | Express 5 (2024年发布的重大版本) |
| zod | ^4.3.6 | 最新主版本,TypeScript 验证标准 |
| undici | ^7.22.0 | Node.js 官方 HTTP 客户端 |
| sharp | ^0.34.5 | 活跃维护的图像处理库 |
| playwright-core | 1.58.2 | 微软维护,稳定更新 |
| chokidar | ^5.0.0 | 主版本 5 (最新) |
部分依赖需要持续关注其发展状态:
// 需要监控的依赖
const watchList = {
"@whiskeysockets/baileys": "7.0.0-rc.9", // RC 版本,非官方 WhatsApp API
"linkedom": "^0.18.12", // 预 1.0 版本,API 可能变更
"@buape/carbon": "0.14.0", // 预 1.0 Discord 框架
"pdfjs-dist": "^5.4.624" // 版本号较大,关注兼容性
};
@whiskeysockets/baileys 是 WhatsApp 非官方 API 客户端,存在被 WhatsApp 官方封禁的风险。建议制定备选方案(如 WhatsApp Business API)。
@buape/carbon 用于 Discord 集成,处于活跃开发但尚未达到 1.0 稳定版。API 变更风险中等。
项目通过 pnpm overrides 机制主动修复了多个已知安全漏洞:
{
"pnpm": {
"overrides": {
"fast-xml-parser": "5.3.4",
"form-data": "2.5.4",
"qs": "6.14.2",
"tar": "7.5.7",
"tough-cookie": "4.1.3"
}
}
}
这表明团队具备良好的安全意识,主动跟踪并修复传递依赖中的 CVE 漏洞。
项目集成了多个通信平台,各 SDK 的时效性状态如下:
| 平台 | SDK | 版本 | 维护状态 |
|---|---|---|---|
| Telegram | grammy | ^1.40.0 | ✅ 活跃 |
| Slack | @slack/bolt | ^4.6.0 | ✅ 官方维护 |
| Discord | @buape/carbon | 0.14.0 | ⚠️ 预1.0 |
| LINE | @line/bot-sdk | ^10.6.0 | ✅ 主版本10 |
| 飞书 | @larksuiteoapi/node-sdk | ^1.59.0 | ✅ 活跃 |
整体评级:✅ 优秀 - 未来就绪
项目技术栈展现了以下特点:
建议行动项:
| 优先级 | 行动 | 时间窗口 |
|---|---|---|
| 中 | Android minSdk 31 → 33 | 2026年前 |
| 低 | 监控 @buape/carbon 1.0 发布 | 持续 |
| 低 | 评估 @whiskeysockets/baileys 替代方案 | 持续 |
| 低 | 关注 linkedom 1.0 API 稳定化 | 持续 |
🔵 Node.js 22 LTS 提供长期支持至 2027 年 (info)
项目采用 Node.js 22.12.0+,为当前 LTS 版本,无需担心近期 EOL 风险
🔵 TypeScript 5.9 与 Rust 工具链展现技术前瞻性 (info)
采用 tsdown、oxlint、oxfmt 等 Rust 生态工具,性能优于传统 JavaScript 工具链
⚪ Android minSdk 31 需在 2026 年前升级 (low)
Android 12 (API 31) 设备将在 2026-2027 年退出主流支持,建议升级至 minSdk 33
⚪ 部分预发布依赖需要持续监控 (low)
@buape/carbon、linkedom、@whiskeysockets/baileys 处于预1.0或RC阶段,API可能变更
🔵 主动安全补丁管理实践良好 (info)
通过 pnpm overrides 修复传递依赖中的已知漏洞 (fast-xml-parser, tar, qs 等)
{
"compilerOptions": {
"target": "ES2023",
"module": "NodeNext",
"strict": true,
"lib": ["DOM", "DOM.Iterable", "ES2023", "ScriptHost"]
}
}
{
"pnpm": {
"overrides": {
"fast-xml-parser": "5.3.4",
"form-data": "2.5.4",
"qs": "6.14.2",
"tar": "7.5.7",
"tough-cookie": "4.1.3"
}
}
}
package.jsontsconfig.jsonapps/android/app/build.gradle.ktsapps/macos/Package.swiftapps/ios/OpenClawKit/Package.swiftOpenClaw 采用统一的服务抽象层设计,支持 7 个以上的部署平台,包括 macOS、Linux、Windows、iOS、Android 以及容器环境(Docker/Podman)。这种跨平台架构的核心在于 resolveGatewayService() 函数,它能够自动检测当前运行平台并返回相应的 GatewayService 实现,使得上层业务逻辑无需关心底层平台差异。理解这套平台抽象机制对于规划迁移策略和维护多平台部署至关重要。
GatewayService 接口定义了所有平台必须实现的统一操作集,包括 install(安装服务)、uninstall(卸载服务)、stop(停止服务)、restart(重启服务)、isLoaded(检查加载状态)、readCommand(读取启动命令)和 readRuntime(读取运行时状态)。这种设计遵循了适配器模式,每个平台提供自己的实现,但对外暴露相同的接口。
// src/daemon/service.ts - 平台检测与服务实例化
export function resolveGatewayService(): GatewayService {
// 自动检测当前平台并返回对应实现
if (process.platform === 'darwin') {
return new LaunchdService(); // macOS: launchd
} else if (process.platform === 'linux') {
return new SystemdService(); // Linux: systemd
} else if (process.platform === 'win32') {
return new SchtasksService(); // Windows: Task Scheduler
}
throw new Error('Unsupported platform');
}
// 统一的服务运行时状态类型
export type GatewayServiceRuntime = {
status?: "running" | "stopped" | "unknown";
state?: string;
subState?: string;
pid?: number;
lastExitStatus?: number;
lastExitReason?: string;
lastRunResult?: string;
lastRunTime?: string;
detail?: string;
cachedLabel?: boolean;
missingUnit?: boolean;
};
设计优势:
GatewayService 接口macOS 使用 launchd 作为服务管理器,配置文件为 plist 格式,存储在 ~/Library/LaunchAgents/ai.openclaw.*.plist。实现代码位于 src/daemon/launchd.ts(442 行),提供完整的生命周期管理。
关键特性:
最低要求:macOS 15+(原生应用),Swift 6.2
Linux 使用 systemd 用户单元,配置文件位于 ~/.config/systemd/user/*.service。实现代码在 src/daemon/systemd.ts(419 行),采用三步升级流程确保服务平滑过渡。
// Linux systemd 滚动升级实现
export async function installSystemdService({
env, stdout, programArguments, workingDirectory, environment, description
}): Promise<{ unitPath: string }> {
await assertSystemdAvailable();
const unit = buildSystemdUnit({
description, programArguments, workingDirectory, environment
});
await fs.writeFile(unitPath, unit, "utf8");
// 三步升级流程:reload -> enable -> restart
// 1. 重新加载配置(识别新的 unit 文件)
const reload = await execSystemctl(["--user", "daemon-reload"]);
// 2. 启用服务(确保开机自启)
const enable = await execSystemctl(["--user", "enable", unitName]);
// 3. 重启服务(应用新配置)
const restart = await execSystemctl(["--user", "restart", unitName]);
return { unitPath };
}
注意事项:需要启用用户 lingering(loginctl enable-linger $USER)以允许用户服务在非登录时运行。
Windows 使用任务计划程序(schtasks),启动脚本为 gateway.cmd。实现代码位于 src/daemon/schtasks.ts(343 行),采用强制创建模式覆盖旧任务。
特点:
/F 标志强制覆盖现有任务Docker 配置要点:
node:22-bookwormPodman 特性:
setup-podman.sh 脚本自动配置| 平台 | 最低版本 | 开发工具 | 关键依赖 |
|---|---|---|---|
| iOS | iOS 18 | Xcode 16+ | SwiftUI, OpenClawKit |
| Android | SDK 31 | Gradle, Kotlin 2.2 | CameraX, Compose UI |
| macOS App | macOS 15 | Swift 6.2 | SwabbleCore, VoiceWake |
所有平台遵循统一的四步升级流程:停止 → 卸载旧版 → 安装新版 → 验证。这种流程确保了升级的原子性和可回滚性。
macOS 平台提供了智能的遗留版本检测和安全清理机制:
// macOS 遗留版本检测与安全清理
export async function findLegacyLaunchAgents(
env: Record<string, string | undefined>
): Promise<LegacyLaunchAgent[]> {
const domain = resolveGuiDomain();
const results: LegacyLaunchAgent[] = [];
// 遍历所有可能的遗留标签
for (const label of resolveLegacyGatewayLaunchAgentLabels(env.OPENCLAW_PROFILE)) {
const plistPath = resolveLaunchAgentPlistPathForLabel(env, label);
const res = await execLaunchctl(["print", `${domain}/${label}`]);
const loaded = res.code === 0;
if (loaded || exists) {
results.push({ label, plistPath, loaded, exists });
}
}
return results;
}
export async function uninstallLaunchAgent({ env, stdout }): Promise<void> {
// 1. 从 launchd 卸载
await execLaunchctl(["bootout", domain, plistPath]);
await execLaunchctl(["unload", plistPath]);
// 2. 安全删除:移动到废纸篓而非直接删除
const dest = path.join(trashDir, `${label}.plist`);
await fs.rename(plistPath, dest);
}
设计亮点:
系统在服务描述中嵌入版本信息,便于运维人员快速识别当前运行版本:
// 版本信息嵌入服务描述
export function formatGatewayServiceDescription(params?: {
profile?: string;
version?: string;
}): string {
const parts: string[] = [];
if (profile) parts.push(`profile: ${profile}`);
if (version) parts.push(`v${version}`);
// 输出示例: "OpenClaw Gateway (profile: dev, v1.2.3)"
return parts.length === 0
? "OpenClaw Gateway"
: `OpenClaw Gateway (${parts.join(", ")})`;
}
通过 profile 机制支持同一机器上运行多个独立实例:
| 平台 | 服务命名格式 | 示例 |
|---|---|---|
| macOS | ai.openclaw.{profile} |
ai.openclaw.dev |
| Linux | openclaw-gateway-{profile} |
openclaw-gateway-staging |
| Windows | OpenClaw Gateway ({profile}) |
OpenClaw Gateway (production) |
每个平台提供专用的状态查询命令用于验证升级成功:
# macOS 验证
launchctl print gui/{uid}/ai.openclaw.gateway
# Linux 验证
systemctl --user show openclaw-gateway.service
# Windows 验证
schtasks /Query /TN "OpenClaw Gateway"
运行时状态包含丰富的诊断信息:status(运行/停止/未知)、pid(进程ID)、lastExitStatus(上次退出码)、lastRunTime(上次运行时间)等,便于快速定位升级问题。
| 依赖项 | 当前版本 | EOL 时间 | 迁移建议 |
|---|---|---|---|
| Node.js | 22 LTS | 2027年4月 | 提前规划 Node 24 迁移 |
| iOS/macOS | 18/15 | ~2027年 | 跟随 Apple 年度更新 |
| Android SDK | 31 | ~2026年(高风险) | 优先升级至 SDK 33+ |
Android SDK 31 风险提示:由于 Google Play 的目标 API 级别要求逐年提高,SDK 31 可能在 2026 年面临兼容性问题,建议尽早规划升级。
所有平台支持通过 OPENCLAW_* 环境变量覆盖默认配置:
| 变量 | 用途 | 示例 |
|---|---|---|
OPENCLAW_PROFILE |
指定配置文件 | dev, staging |
OPENCLAW_STATE_DIR |
状态目录 | ~/.openclaw-dev/state/ |
OPENCLAW_GATEWAY_TOKEN |
Gateway 认证令牌 | 安全存储的密钥 |
这种设计允许在不修改代码的情况下调整部署配置,特别适合 CI/CD 流水线和容器化部署场景。
🔵 统一的跨平台服务抽象层 (info)
通过 GatewayService 接口和 resolveGatewayService() 工厂函数实现平台无关的服务管理
⚪ Android SDK 31 EOL 风险 (medium)
Android SDK 31 可能在 2026 年面临 Google Play 兼容性问题,建议优先规划升级至 SDK 33+
⚪ Linux 用户 lingering 依赖 (low)
systemd 用户服务需要启用 loginctl enable-linger 才能在非登录时运行
🔵 安全的遗留版本清理 (info)
macOS 卸载时将 plist 移动到废纸篓而非直接删除,支持回滚
export function resolveGatewayService(): GatewayService {
if (process.platform === 'darwin') {
return new LaunchdService();
} else if (process.platform === 'linux') {
return new SystemdService();
} else if (process.platform === 'win32') {
return new SchtasksService();
}
throw new Error('Unsupported platform');
}
const reload = await execSystemctl(["--user", "daemon-reload"]);
const enable = await execSystemctl(["--user", "enable", unitName]);
const restart = await execSystemctl(["--user", "restart", unitName]);
src/daemon/service.tssrc/daemon/launchd.tssrc/daemon/systemd.tssrc/daemon/schtasks.tsDockerfilesrc/daemon/constants.ts展示 OpenClaw monorepo 的整体结构,包括入口点层次、核心模块组织、构建系统和测试基础设施的关系
Complexity: 21 nodes, 20 edges
Related files:
展示 OpenClaw 多渠道集成的分层架构,包括 ChannelManager、Plugin Registry、各平台适配器及其与网关的交互关系
Complexity: 22 nodes, 24 edges
Related files:
展示 OpenClaw 插件系统的核心组件关系,包括插件发现、加载、注册和运行时依赖注入的完整流程
Complexity: 23 nodes, 24 edges
Related files:
src/plugins/registry.tssrc/plugins/loader.tssrc/plugins/runtime.tssrc/plugins/api.tssrc/hooks/hook-runner.ts展示智能体执行引擎的核心组件和执行流程,包括会话管理、模型回退、车道队列和事件系统的协作关系
Complexity: 16 nodes, 15 edges
展示智能体的层级结构、会话键命名规范以及智能体间的通信路径和控制机制
Complexity: 13 nodes, 19 edges
展示 OpenClaw 的跨平台部署架构,包括服务抽象层、桌面平台后端、容器化部署和云平台集成。GatewayService 接口统一了所有平台的服务生命周期管理。
Complexity: 19 nodes, 17 edges
Related files:
src/daemon/service.tssrc/daemon/launchd.tssrc/daemon/systemd.tssrc/daemon/schtasks.tsfly.tomlrender.yamlscripts/podman/openclaw.container.in展示 OpenClaw 项目的依赖分层结构,包括核心框架、消息平台 SDK、AI/ML 集成和扩展系统的关系
Complexity: 22 nodes, 22 edges
Related files:
展示 OpenClaw 日志系统的核心组件和数据流向,包括子系统日志器、双输出通道、诊断事件系统和敏感数据脱敏层
Complexity: 14 nodes, 14 edges
Related files:
展示项目中死代码的分类分布、严重程度以及建议的清理优先级流程
Complexity: 14 nodes, 13 edges
Related files:
src/memory/manager-sync-ops.tssrc/memory/manager-embedding-ops.tssrc/compat/legacy-names.tssrc/memory/openai-batch.ts展示 OpenClaw 的多层认证体系,包括网关认证、渠道认证和审计系统的整体架构关系
Complexity: 19 nodes, 24 edges
Related files:
src/gateway/auth.tssrc/gateway/auth-rate-limit.tssrc/security/audit.tssrc/security/external-content.tssrc/security/secret-equal.tssrc/channel/channel-manager.ts展示了从消息入站到响应渲染的完整安全处理管道,包括外部内容保护、身份验证、授权检查和输出净化等多个安全层
Complexity: 18 nodes, 17 edges
Related files:
src/security/external-content.tssrc/channels/sender-identity.tssrc/auto-reply/reply/inbound-context.tsui/src/ui/markdown.tssrc/channels/command-gating.ts展示 OpenClaw 密钥管理系统的数据流、安全层次和保护机制,从环境变量注入到运行时使用的完整生命周期
Complexity: 14 nodes, 17 edges
Related files:
src/config/env-substitution.tssrc/config/config-paths.tssrc/security/audit.tssrc/security/audit-extra.sync.tssrc/security/secret-equal.tssrc/logging/redact.tssrc/media/store.ts展示 OpenClaw 的文件系统存储架构,包括核心存储模块、缓存层和各类专用存储之间的关系
Complexity: 18 nodes, 15 edges
Related files:
src/config/sessions/store.tssrc/media/store.tssrc/cron/store.tssrc/agents/auth-profiles/store.tssrc/memory/memory-schema.ts展示六套 Vitest 配置如何组织不同类型的测试,以及测试隔离机制的工作原理
Complexity: 16 nodes, 15 edges
Related files:
vitest.config.tsvitest.unit.config.tsvitest.e2e.config.tsvitest.live.config.tstest/setup.tstest/test-env.ts展示 OpenClaw 文档系统的整体架构,包括多语言支持、内容组织和翻译工作流程
Complexity: 16 nodes, 18 edges
Related files:
展示 OpenClaw 项目各技术组件的版本状态、EOL 时间线和风险等级,帮助团队规划升级优先级
Complexity: 18 nodes, 14 edges
Related files:
展示 OpenClaw 各平台服务管理的统一抽象层和升级流程,包括 macOS(launchd)、Linux(systemd)、Windows(schtasks)及容器环境的服务生命周期管理。
Complexity: 18 nodes, 14 edges
Related files:
Priority: high Effort: low
将默认 DM 策略从开放改为 allowlist,启用 Gateway 认证(token/password),移除通配符允许列表,启用设备认证和访问组控制。这些是生产环境部署的关键安全要求。
Priority: high Effort: high
重构 manager-sync-ops.ts(1078 行)和 manager-embedding-ops.ts(799 行)两个文件,添加完整类型定义,移除 @ts-nocheck 注释。这是代码质量的最大单点改进机会。
Priority: medium Effort: medium
迁移 17 个已弃用导出(Config、SDK、BodyForAgent),移除 openai-batch.ts 重导出,清理 5 处空数组定义,建立 10 个遗留文件的清理时间表。
Priority: medium Effort: medium
当前 Extensions 目录无测试文件,需要为插件扩展系统添加单元测试和集成测试,确保插件 API 的稳定性和向后兼容性。
Priority: medium Effort: low
监控 @whiskeysockets/baileys 稳定版本发布(当前 RC)、评估 sqlite-vec alpha 状态风险、审查 @buape/carbon 和 linkedom 依赖。设置依赖版本升级跟踪流程。
Priority: low Effort: high
当前 20+ 适配器接口可能过于复杂,考虑合并相关适配器、引入适配器分层或必选/可选分组,降低新渠道集成的认知负担。
Priority: low Effort: low
将 Android minSdk 从 31 升级到 33+,确保 2027 年后的设备支持。当前 minSdk 31 将于 2026-27 年进入 EOL 风险期。
Generated by Limo - an AI-powered application analysis engine