关键词:ACP RPC|历史截断|运行中止|消息注入|内存安全|会话控制
在 OpenClaw 架构中,src/core/chat.ts 是智能体与外部世界交互的核心 RPC 接口层。它不仅处理用户消息的收发,更承担着会话状态管理、资源保护与紧急干预等关键职责。
所有客户端(Web、WhatsApp、CLI)均通过 ACP(Agent Communication Protocol)调用 chat.* 方法,而 chat.ts 则确保这些操作在高并发、长上下文、多会话环境下依然安全、高效、可控。
本文将详解其三大核心接口的设计与实现:
chat.history:按字节截断防 OOMchat.abort:按 session 或 runId 精准中止chat.inject:管理员手动注入消息(审计/调试)chat.ts 在系统中的位置
chat.ts 是 ACP 方法的实现入口,不直接执行 AI 逻辑,而是协调会话状态与执行引擎。
chat.history —— 安全获取对话历史// chat.ts → history()
export async function history(
sessionKey: string,
maxBytes: number = 8192 // 默认 8KB
): Promise<ChatMessage[]> {
const rawHistory = await sessionStore.getMessages(sessionKey);
// 从最新消息开始累加,直到接近 maxBytes
let totalBytes = 0;
const result: ChatMessage[] = [];
for (let i = rawHistory.length - 1; i >= 0; i--) {
const msg = rawHistory[i];
const msgBytes = new TextEncoder().encode(JSON.stringify(msg)).length;
if (totalBytes + msgBytes > maxBytes) break;
result.unshift(msg); // 保持时间顺序
totalBytes += msgBytes;
}
return result;
}maxBytes=65536 加载更多历史可用,但绝不失控。
chat.abort —— 精准中止运行中的任务runId 中止(精准打击)每次工具调用或 LLM 思考生成唯一 runId:
// agent-runner.ts
const runId = nanoid();
activeRuns.set(runId, { sessionKey, abortController });chat.abort 支持按 runId 终止:
// chat.ts → abort()
export function abort(params: { runId?: string; sessionKey?: string }) {
if (params.runId) {
const run = activeRuns.get(params.runId);
if (run) {
run.abortController.abort(); // 触发 AbortSignal
activeRuns.delete(params.runId);
logger.info(`Aborted run ${params.runId}`);
}
} else if (params.sessionKey) {
// 中止该会话所有运行(见下文)
}
}sessionKey 中止(全面清理)if (params.sessionKey) {
for (const [runId, run] of activeRuns.entries()) {
if (run.sessionKey === params.sessionKey) {
run.abortController.abort();
activeRuns.delete(runId);
}
}
// 同时清除会话输入锁(防止卡死)
inputLocks.delete(params.sessionKey);
}AbortSignal所有异步操作必须监听信号:
// exec.ts
const { signal } = abortController;
await execa(cmd, { signal }); // execa 支持 AbortSignal
// llm.ts
const response = await fetch(llmEndpoint, { signal });中止不是建议,而是强制指令。
chat.inject —— 管理员消息注入role: "system"// chat.ts → inject()
export async function inject(
sessionKey: string,
content: string,
opts: { asSystem?: boolean } = {}
) {
// 1. 权限检查
if (!currentACPContext.isAdmin) {
throw new Error("Permission denied");
}
// 2. 构造消息
const message: ChatMessage = {
id: generateId(),
role: opts.asSystem ? 'system' : 'user',
content,
timestamp: Date.now(),
injectedBy: currentACPContext.userId // 审计字段
};
// 3. 写入会话历史
await sessionStore.appendMessage(sessionKey, message);
// 4. 若会话空闲,触发 AI 响应
if (!inputLocks.has(sessionKey)) {
await agentRunner.processMessage(sessionKey, message);
}
logger.info(`Injected message into ${sessionKey} by admin`);
}{
"method": "chat.inject",
"params": {
"sessionKey": "wa:+1234567890",
"content": "注意:数据库备份正在进行,请勿重启服务。",
"asSystem": true
}
}注入是特权,不是后门。
sessionKey 有 inputLocks 标记.jsonl 文件(原子 append)AbortController 从 chat.abort 一路传递至: execa)
接口即契约,安全即默认。
chat.ts 的设计哲学是:赋予用户充分的控制能力(查询、中止、注入),同时由系统承担安全与稳定性责任。无论是防止内存爆炸的历史截断,还是精准到毫秒的运行中止,都体现了对资源与体验的双重尊重。
这不仅是 RPC 接口,更是人机协作的操作协议——清晰、可靠、可审计。
在下一篇中,我们将探讨 OpenClaw 的部署模型演进:从单机 Docker 到 Kubernetes Operator。
下一篇预告: 第 18 篇:OpenClaw 架构下Skills System —— 为什么“文档即工具”是 OpenClaw 的扩展灵魂
您的 AI 助手,从此由您定义。若感兴趣可以浏览本书其他章节内容:
openclaw.mjs、config.yaml 与环境变量管理run.ts 上篇 —— 模型调度、账号轮询与上下文守护机制run.ts 下篇 —— 故障转移、重试策略与结果封装memory-search.ts 中的 RAG 配置解析与合并逻辑exec.ts 上篇 —— 安全执行 Shell 命令的三层隔离模型exec.ts 下篇 —— 用户审批、后台任务与权限提升控制process.ts —— AI 如何像开发者一样管理后台进程server-channels.ts —— 渠道插件生命周期管理器session.ts 与 Baileys 的健壮连接管理monitor-inbox.ts 如何解析、去重与防抖chat.ts 中的历史查询、发送与中止逻辑ws-log.ts 如何让 WebSocket 日志可读可用