OpenClaw 的聊天 RPC 接口 —— chat.ts 中的历史查询、发送与中止逻辑
关键词: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 —— 安全获取对话历史
需求
- 用户切换设备时需加载历史
- Web UI 需渲染完整聊天记录
- 但历史可能长达数 MB,直接返回会导致 OOM
解法:按字节截断 + 最近优先
// 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;
}
关键特性
- 默认 8KB:足够显示最近 10~20 条消息
- 可配置上限:Web UI 可请求
maxBytes=65536加载更多 - 永不超限:即使会话有 100MB 历史,也只返回安全片段
历史可用,但绝不失控。
三、接口二:chat.abort —— 精准中止运行中的任务
场景
- 用户发送“取消”命令
- 管理员发现危险操作需紧急中断
- 超时自动终止
挑战
- 一个会话可能有多个并行任务(如后台日志监听 + 前台命令)
- 需区分“中止整个会话” vs “中止特定运行”
解法:两级中止机制
1. 按 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) {
// 中止该会话所有运行(见下文)
}
}
2. 按 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 —— 管理员消息注入
用途
- 审计:模拟用户提问测试行为
- 调试:在特定上下文插入指令
- 紧急通知:“系统将在 5 分钟后维护”
安全约束
- 仅限管理员(通过 ACP 认证上下文校验)
- 不可伪造用户身份
- 注入消息标记为
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`);
}
使用示例(ACP 调用)
{
"method": "chat.inject",
"params": {
"sessionKey": "wa:+1234567890",
"content": "注意:数据库备份正在进行,请勿重启服务。",
"asSystem": true
}
}
注入是特权,不是后门。
五、内存与并发安全
1. 会话锁(Input Lock)
- 每个
sessionKey有inputLocks标记 - 防止用户连发消息导致 LLM 并行推理(浪费资源)
2. 历史读写分离
- 写入:追加到
.jsonl文件(原子 append) - 读取:内存缓存 + 文件 fallback,避免频繁 I/O
3. 中止信号传播
AbortController从chat.abort一路传递至:- HTTP 请求(LLM 调用)
- 子进程(
execa) - 远程节点连接(SSH)
六、ACP 接口规范

接口即契约,安全即默认。
结语:控制权交给用户,责任留在系统
chat.ts 的设计哲学是:赋予用户充分的控制能力(查询、中止、注入),同时由系统承担安全与稳定性责任。无论是防止内存爆炸的历史截断,还是精准到毫秒的运行中止,都体现了对资源与体验的双重尊重。
这不仅是 RPC 接口,更是人机协作的操作协议——清晰、可靠、可审计。
在下一篇中,我们将探讨 OpenClaw 的部署模型演进:从单机 Docker 到 Kubernetes Operator。
下一篇预告:
第 18 篇:OpenClaw 架构下Skills System —— 为什么“文档即工具”是 OpenClaw 的扩展灵魂
您的 AI 助手,从此由您定义。若感兴趣可以浏览本书其他章节内容:
浙公网安备 33010602011771号