逐模块还原 OpenClaw 的内核
深入 src/ 源码,逐模块还原真实实现——Gateway 入口、ACP 会话生成、MCP Client、插件系统、沙箱隔离。所有结论来自源码文本,含实际函数签名与类型定义。
# openclaw/openclaw · TypeScript · pnpm monorepo
Gateway 入口与主循环
源码:src/entry.ts(Node.js CLI 入口点)。整个系统从 runMainOrRootHelp() 开始。
// entry.ts:92-94
const entryFile = fileURLToPath(import.meta.url);
const installRoot = resolveEntryInstallRoot(entryFile);
// entry.ts:285
async function runMainOrRootHelp(argv: string[]): Promise<void> {
const { runCli } = await gatewayEntryStartupTrace.measure("cli", async () => {
const parsed = parseCliProfileArgs(parsedContainer.argv);
// ...profile env applied
await runCli({ argv: parsed.argv });
});
}
启动链路:entry.ts → resolveEntryInstallRoot() → respawnWithoutOpenClawCompileCacheIfNeeded()(编译缓存优化)→ parseCliProfileArgs()(加载 profile 环境变量)→ runCli()。Gateway 模式下实际跑的是 runCli 内部的 gateway 子命令。
编译缓存通过 entry.compile-cache.ts 管理,用 OPENCLAW_COMPILE_CACHE_DIR 环境变量定位缓存目录,重 spawn 时不带缓存标志以避免递归。
respawnWithoutOpenClawCompileCacheIfNeeded() 会以正确标志重启一个干净子进程;重 spawn 时剥掉缓存标志正是为了避免无限递归自我重启。启动 trace
gatewayEntryStartupTrace.measure() 把 CLI 解析包成一段可观测的计时区间,冷启动耗时由此打点。
多 profile 隔离
parseCliProfileArgs() 先于 runCli() 执行,把 profile 对应的环境变量套到进程上,实现多账号/多环境切换。
常驻模式
Gateway 模式实际是 runCli 内部的 gateway 子命令,启动后转入 WebSocket 服务长循环。
ACP 会话生成
源码:src/agents/acp-spawn.ts(1708 行,核心文件)。ACP(Agent Client Protocol)的会话生成逻辑在此文件。
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
import {
cleanupFailedAcpSpawn,
type AcpSpawnRuntimeCloseHandle,
} from "../acp/control-plane/spawn.js";
import { isAcpEnabledByPolicy, resolveAcpAgentPolicyError } from "../acp/policy.js";
import { readAcpSessionMeta } from "../acp/runtime/session-meta.js";
import { resolveAcpSessionCwd, resolveAcpThreadSessionDetailLines }
from "@openclaw/acp-core/runtime/session-identifiers";
import type { AcpRuntimeSessionMode } from "@openclaw/acp-core/runtime/types";
ACP Core 是一个独立 package(@openclaw/acp-core),包含 session 标识符解析、运行时类型定义(AcpRuntimeSessionMode)以及会话元数据读写(session-meta.js)。
会话生成流程:acp-spawn.ts 调用 getAcpSessionManager() 获取会话管理器 → 通过 spawn.js 中的 cleanupFailedAcpSpawn() 处理失败清理 → 由 policy.js 的 isAcpEnabledByPolicy() 做策略检查。父进程通过 acp-spawn-parent-stream.ts 做 stdout/parent-stream relay,把 ACP harness 的输出打回到 OpenClaw 的会话流中。
会话限制由 config/agent-limits.ts 控制:
DEFAULT_SUBAGENT_MAX_CHILDREN_PER_AGENT // 每个 agent 最大子会话数
DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH // 最大 spawn 深度(防止嵌套失控)
会话管理器
getAcpSessionManager() 返回进程级 ACP session manager,负责登记、查找与回收每个 ACP 会话句柄。
失败清理
cleanupFailedAcpSpawn() 在 spawn 半途失败时回滚已分配资源,AcpSpawnRuntimeCloseHandle 是对应的关闭句柄类型。
策略闸门
isAcpEnabledByPolicy() 决定该 agent 是否允许走 ACP,resolveAcpAgentPolicyError() 给出被拒原因。
acp-spawn-parent-stream.ts 负责把子进程 stdout 转回父进程的会话流——resolveAcpThreadSessionDetailLines() / readAcpSessionMeta() 则在 @openclaw/acp-core 一侧解析会话标识与元数据。MAX_CHILDREN_PER_AGENT(扇出宽度)与 MAX_SPAWN_DEPTH(递归深度)双重约束,二者共同防止 sub-agent 失控自我繁殖。AgentHarness 类型系统
源码:src/agents/harness/types.ts。OpenClaw 的外部编程接口通过 AgentHarness 接口抽象——这是 ACP harness(Claude Code / Codex 等)接入的核心协议。
export type AgentHarness = AgentHarnessRunCapability &
AgentHarnessSideQuestionCapability &
AgentHarnessClassificationCapability &
AgentHarnessCompactionCapability &
AgentHarnessSessionLifecycleCapability;
// 核心能力:
export type AgentHarnessRunCapability = {
run(params: AgentHarnessAttemptParams): Promise<AgentHarnessAttemptResult>;
};
export type AgentHarnessSideQuestionCapability = {
sideQuestion(params: AgentHarnessSideQuestionParams): Promise<AgentHarnessSideQuestionResult>;
};
export type AgentHarnessClassificationCapability = {
classify?(input: unknown): Promise<AgentHarnessResultClassification>;
};
export type AgentHarnessCompactionCapability = {
compact(params: AgentHarnessCompactParams): Promise<AgentHarnessCompactResult>;
};
export type AgentHarnessSessionLifecycleCapability = {
reset?(params: AgentHarnessResetParams): Promise<void>;
};
关键点:run() 是必须方法,sideQuestion()(侧提问)、classify()(结果分类)、compact()(压缩会话)、reset() 都是可选扩展。OpenClaw 通过这个统一接口驱动 Claude Code、Cursor、Gemini CLI 等外部 harness。
主运行能力
每次 attempt 走 run(),输入 AttemptParams,输出 AttemptResult。
会话压缩
把长 transcript 压成摘要,配合 classify() 做结果分类。
生命周期
会话级重置,让外部 harness 回到干净起点。
MCP 运行时
源码:src/agents/agent-bundle-mcp-runtime.ts。OpenClaw 的 MCP 客户端实现,基于 @modelcontextprotocol/sdk。
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
// Session-scoped MCP session 结构
type BundleMcpSession = {
serverName: string;
client: Client;
transport: Transport;
transportType: "stdio" | "sse" | "streamable-http";
requestTimeoutMs: number;
supportsParallelToolCalls: boolean;
detachStderr?: () => void;
};
// 全局 session manager 符号(保证每个进程只有一个 manager 实例)
const SESSION_MCP_RUNTIME_MANAGER_KEY = Symbol.for("openclaw.sessionMcpRuntimeManager");
工具调用链:Client.listTools() → 获取工具列表 → Client.callTool() 调用工具 → 结果通过 CallToolResult 类型返回。超时由 BUNDLE_MCP_CATALOG_LIST_TIMEOUT_MS = 1500ms 控制,失败重试用指数退避(BUNDLE_MCP_FAILURE_COOLDOWN_MS = 60000ms)。
export function createBundleMcpJsonSchemaValidator(): jsonSchemaValidator;
export function resolveSessionMcpConfigSummary(params: {...}): {...};
export function resolveSessionMcpConfigFingerprint(params: {...}): string;
function resolveSessionMcpRuntimeIdleTtlMs(cfg?: OpenClawConfig): number;
// 默认值:10 分钟 idle TTL
MCP 服务器配置通过 loadEmbeddedAgentMcpConfig() 加载(embedded-agent-mcp.ts),由 mcp-config-shared.ts 的 isMcpConfigRecord() 做类型校验,传输解析在 mcp-transport.ts 中。
| transportType | 承载方式 | 典型场景 |
|---|---|---|
stdio | 子进程 stdin/stdout 管道 | 本地 MCP server 可执行文件 |
sse | Server-Sent Events 长连接 | 远端只读流式 server |
streamable-http | StreamableHTTPClientTransport | HTTP 双向 / 可恢复会话 |
| 常量 | 值 | 作用 |
|---|---|---|
BUNDLE_MCP_CATALOG_LIST_TIMEOUT_MS | 1500ms | listTools() 目录拉取超时 |
BUNDLE_MCP_FAILURE_COOLDOWN_MS | 60000ms | 失败后指数退避冷却窗 |
| idle TTL | 10 分钟 | resolveSessionMcpRuntimeIdleTtlMs() 空闲回收 |
进程单例
SESSION_MCP_RUNTIME_MANAGER_KEY 用全局 Symbol 注册表锚定 manager,保证每进程唯一一个 MCP runtime manager,跨模块复用同一实例。
配置指纹
resolveSessionMcpConfigFingerprint() 把会话级 MCP 配置哈希成指纹;配置不变则复用已建连接,变更才重建 session。
插件系统公共接口
源码:src/plugins/types.ts(定义了 OpenClawPluginApi 所有公共注册方法)。插件通过 buildPluginApi() 构建的 API 对象注册自己的能力。
export type BuildPluginApiParams = {
id: string;
name: string;
version?: string;
source: string;
registrationMode: OpenClawPluginApi["registrationMode"];
config: OpenClawConfig;
runtime: PluginRuntime;
logger: PluginLogger;
handlers?: Partial<Pick<OpenClawPluginApi,
| "registerTool" // 注册工具
| "registerHook" // 注册生命周期钩子
| "registerHttpRoute" // 注册 HTTP 路由
| "registerChannel" // 注册消息渠道
| "registerGatewayMethod" // 注册 Gateway 方法
| "registerCli" // 注册 CLI 命令
| "registerProvider" // 注册 Provider(模型/语音/图像等)
| "registerAgentHarness" // 注册外部 Agent Harness(ACP)
| "registerMcpServer" // 注册 MCP 服务器
| ...(共 30+ 项)
>>;
};
每个注册方法对应一个具体的功能领域。例如 registerTool 让插件声明自己的工具,registerHook 注入生命周期回调(start/stop/enable/disable 等),registerAgentHarness 是 ACP 外部 harness 接入的正式入口。
| 能力域 | 注册方法 | 插件能做什么 |
|---|---|---|
| 模型与执行 | registerTool · registerProvider · registerMcpServer | 暴露工具、接入模型/语音/图像 provider、挂载 MCP server |
| 外部 Agent | registerAgentHarness | 把 Claude Code / Codex 等 ACP harness 接进来 |
| 消息与路由 | registerChannel · registerHttpRoute · registerGatewayMethod | 新增渠道、HTTP 端点、Gateway RPC 方法 |
| 生命周期与 CLI | registerHook · registerCli | 注入 start/stop/enable/disable 钩子、扩展 CLI 命令 |
BuildPluginApiParams.handlers 用 Partial<Pick<OpenClawPluginApi, …>> 描述——插件只实现自己关心的注册方法,未提供的能力域自动缺省,宿主据此裁剪 API 表面。Manifest 解析
源码:src/plugins/manifest.ts。插件的元数据描述文件是 openclaw.plugin.json(PLUGIN_MANIFEST_FILENAME 常量)。
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";
export const MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024;
// manifest 解析缓存(SLRU,512 条目)
const pluginManifestLoadCache = new PluginLruCache<PluginManifestLoadCacheEntry>(
MAX_PLUGIN_MANIFEST_LOAD_CACHE_ENTRIES,
);
// 缓存 key 生成
export function createPluginCacheKey(...): PluginCacheKey;
// 清除缓存(测试用)
export function clearPluginManifestLoadCache(): void;
Manifest 中包含插件版本、入口点、config schema、commands、providers、hooks 等元数据。解析后缓存 512 条,缓存条目包含 mtimeMs/ctimeMs 以跟踪文件变化。Secret provider exec 参数长度限制等安全策略也在此文件校验。
固定约定
PLUGIN_MANIFEST_FILENAME = openclaw.plugin.json,发现插件即按此名查找。
体积上限
MAX_PLUGIN_MANIFEST_BYTES = 256 * 1024,超限直接拒绝解析。
解析缓存
PluginLruCache 缓存 512 条解析结果,重复加载走缓存。
失效跟踪
缓存条目记录 mtimeMs/ctimeMs,文件一变即作废重读。
createPluginCacheKey() 生成稳定缓存键,clearPluginManifestLoadCache() 供测试清空——保证用例之间不串缓存。Plugin 运行时协调器
源码:src/plugins/runtime.ts。Plugin 运行时协调器管理全局 plugin registry 状态。
// runtime.ts — 全局 RegistryState 保存在 globalThis 上
const state: RegistryState = (() => {
const globalState = globalThis as typeof globalThis & {
[PLUGIN_REGISTRY_STATE]?: RegistryState;
};
let registryState = globalState[PLUGIN_REGISTRY_STATE];
if (!registryState) {
registryState = {
activeRegistry: null,
activeVersion: 0,
httpRoute: { registry: null, pinned: false, version: 0 },
channel: { registry: null, pinned: false, version: 0 },
importedPluginIds: new Set<string>(),
runtimeSubagentMode: "default",
};
globalState[PLUGIN_REGISTRY_STATE] = registryState;
}
return registryState;
})();
// 关键函数:
function asPluginRegistry(registry: RegistryState["activeRegistry"]): PluginRegistry | null;
async function cleanupPreviousPluginHostRegistry(params: {...}): Promise<void>;
function isRegistryLive(registry: PluginRegistry): boolean;
function isRegistryPinned(registry: PluginRegistry): boolean;
运行时状态设计:每个进程只有一个全局 registry(存在 globalThis 上),通过 activeRegistry / httpRoute / channel 三个视角观察同一份 registry。pinned 标记用于防止正在使用的 registry 被意外清理。
主视角
当前生效的 registry 与 activeVersion 版本号;热替换时版本自增,旧引用据此识别过期。
路由视角
HTTP 路由专属的 { registry, pinned, version } 三元组——独立 pin,避免请求中途路由表被换。
渠道视角
渠道注册同样独立 pin/version,让消息渠道在重载窗口内保持稳定指向。
isRegistryLive() / isRegistryPinned() 双判后,cleanupPreviousPluginHostRegistry() 才回收旧 host registry——pinned 为真时跳过,确保正在服务请求的 registry 不被拔掉。沙箱隔离模型
源码:src/agents/sandbox/runtime-status.ts。沙箱模式由 sandbox.mode 配置控制。
function shouldSandboxSession(
cfg: SandboxConfig,
sessionKey: string,
mainSessionKey: string,
) {
if (cfg.mode === "off") return false; // 关闭沙箱
if (cfg.mode === "all") return true; // 所有 session 都沙箱
// 默认:main session 不沙箱,其他沙箱
return sessionKey.trim() !== mainSessionKey.trim();
}
export function resolveSandboxRuntimeStatus(params: {
cfg?: OpenClawConfig;
sessionKey?: string;
agentId: string;
}): SandboxRuntimeStatus;
沙箱策略通过 tool-policy.ts 的 classifyToolAgainstSandboxToolPolicy() 对每个工具调用做检查。实际 Docker/SSH 隔离在 sandbox/config.ts 中配置。
| sandbox.mode | 行为 |
|---|---|
off | 不隔离,所有 session 直接跑在宿主环境。 |
all | 全部隔离,每个 session 都进沙箱。 |
non-main (默认) | 只对非主 session 隔离;main session 保持原生权限。 |
状态解析
按 cfg / sessionKey / agentId 算出该 agent 的 SandboxRuntimeStatus——是否隔离、用哪种隔离。
逐工具放行
classifyToolAgainstSandboxToolPolicy() 对每次工具调用分类,决定在沙箱内是允许、降权还是拦截。
off → 永不隔离;all → 永远隔离;默认 non-main 只比较 sessionKey.trim() !== mainSessionKey.trim()——主会话保持原生,派生会话进沙箱。真正的 Docker/SSH 隔离细节落在 sandbox/config.ts。记忆插件:LanceDB / Wiki / Active
源码:packages/memory-host-sdk + extensions/memory-*。OpenClaw 的记忆系统通过可插拔的记忆后端实现,三种内置后端共用一份 MemorySearchManager 契约——满足语义搜索、结构化 vault、即时会话状态三类不同需求。
所有后端实现统一的 MemorySearchManager 接口——search / readFile / status / probeEmbeddingAvailability / probeVectorAvailability。两个 probe* 探针是降级逻辑的支点:嵌入或向量存储不可用时,宿主自动退回纯全文检索而非抛错。记忆自动沉淀挂在 agent_end 钩子上,按 agentId + sessionKey 检索,因此可跨 Telegram / Discord / Slack… 共享同一份记忆。
向量嵌入检索
把文本 embed 成向量存进 LanceDB,按 L2 距离找最近邻,相似度 1/(1+d)。agent_end 自动沉淀,0.95 相似即判重跳过。
Markdown vault
每条记忆是一个 .md 文件(YAML frontmatter + 正文),content_store 原子写入(temp→fsync→rename)保证崩溃安全,全文检索后注入 prompt 段。
会话间活跃上下文
按会话滑动窗口的即时上下文,SessionMemoryState 三增量门控——token+4000 & tool+8 & turn+4 同时越线才触发抽取。
const vector = await embeddings.embed(text);
const existing = await db.search(vector, 1, 0.95); // 0.95 相似 = 重复
if (existing.length > 0) return; // 判重命中 → 跳过
await db.store({ text, vector, importance: 0.7, category });
// embedding 端点挂了 → probeEmbeddingAvailability=false → 退回全文检索,沉淀暂停
完整目录树
以下为 src/ 顶层目录(基于仓库主分支),把前面各节的源码文件放回它们的位置。
openclaw/src/
├── entry.ts # CLI 入口,runMainOrRootHelp()
├── entry.compile-cache.ts # 编译缓存管理
├── entry.respawn.ts # 进程重 spawn 逻辑
│
├── agents/ # Agent 运行时核心
│ ├── acp-spawn.ts # ACP 会话生成(1708 行)
│ ├── acp-runtime-overlay.ts
│ ├── agent-bundle-mcp-runtime.ts # MCP Client 实现
│ ├── agent-bundle-mcp-tools.ts # MCP 工具封装
│ ├── agent-command.ts # Agent 命令处理
│ ├── harness/types.ts # AgentHarness 接口定义
│ ├── sandbox/
│ │ ├── runtime-status.ts # 沙箱模式判断
│ │ ├── config.ts # 沙箱配置
│ │ └── tool-policy.ts # 工具策略分类
│ └── agent-scope.ts # Session / Agent 作用域解析
│
├── plugins/ # 插件系统
│ ├── types.ts # OpenClawPluginApi 公共接口
│ ├── manifest.ts # openclaw.plugin.json 解析
│ ├── api-builder.ts # PluginApi 构建器
│ ├── runtime.ts # PluginRegistry 运行时协调器
│ ├── registry-types.ts # Registry 类型定义
│ ├── registry-lifecycle.ts # Registry 生命周期管理
│ ├── host-hook-runtime.ts # 钩子运行时分发
│ └── hook-types.ts # 钩子类型定义
│
├── channels/ # 消息渠道接入
│ ├── plugins/
│ │ ├── types.plugin.ts # ChannelPlugin 接口
│ │ └── types.core.ts # 核心 channel 类型
│ ├── route-projection.ts # 消息路由映射
│ └── thread-bindings-*.ts # 线程/话题绑定
│
├── config/
│ ├── sessions/
│ │ ├── store.ts # Session 存储
│ │ └── paths.ts # 路径解析
│ ├── agent-limits.ts # 子 agent 限制
│ └── types.openclaw.ts # OpenClaw 配置类型
│
├── acp/ # ACP 协议实现
│ ├── control-plane/
│ │ ├── manager.ts # ACP Session 管理器
│ │ └── spawn.ts # ACP Spawn 逻辑
│ └── policy.ts # ACP 策略检查
│
├── cli/ # CLI 相关
│ ├── argv.ts # 参数解析
│ ├── profile.ts # Profile 环境变量
│ └── program/ # CLI 程序结构
│
├── gateway/ # Gateway WebSocket API
│ ├── server-methods/ # Gateway 方法处理
│ └── call.ts # Gateway 调用封装
│
└── packages/ # 内部共享包
├── acp-core/ # ACP 核心类型与工具
├── agent-core/ # Agent 核心逻辑
├── plugin-sdk/ # 插件 SDK(public)
├── gateway-protocol/ # Gateway 协议定义
├── llm-core/ # LLM 接口
├── memory-host-sdk/ # MemorySearchManager 记忆契约
└── model-catalog-core/ # 模型目录
extensions/ # 141 个可插拔扩展(与 src/ 同级)
├── memory-lancedb/ # 向量嵌入检索(L2 距离)
├── memory-wiki/ # Markdown vault + 全文检索
└── active-memory/ # 会话滑动窗口即时上下文