• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
思想人生从关注生活开始
博客园    首页    新随笔    联系   管理    订阅  订阅

OpenClaw 的聊天 RPC 接口 —— chat.ts 中的历史查询、发送与中止逻辑

关键词:ACP RPC|历史截断|运行中止|消息注入|内存安全|会话控制

在 OpenClaw 架构中,src/core/chat.ts 是智能体与外部世界交互的核心 RPC 接口层。它不仅处理用户消息的收发,更承担着会话状态管理、资源保护与紧急干预等关键职责。

所有客户端(Web、WhatsApp、CLI)均通过 ACP(Agent Communication Protocol)调用 chat.* 方法,而 chat.ts 则确保这些操作在高并发、长上下文、多会话环境下依然安全、高效、可控。

本文将详解其三大核心接口的设计与实现:

  1. chat.history:按字节截断防 OOM
  2. chat.abort:按 session 或 runId 精准中止
  3. chat.inject:管理员手动注入消息(审计/调试)

一、整体架构:chat.ts 在系统中的位置

image

 

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 接口规范

image

接口即契约,安全即默认。

结语:控制权交给用户,责任留在系统

chat.ts 的设计哲学是:赋予用户充分的控制能力(查询、中止、注入),同时由系统承担安全与稳定性责任。无论是防止内存爆炸的历史截断,还是精准到毫秒的运行中止,都体现了对资源与体验的双重尊重。

这不仅是 RPC 接口,更是人机协作的操作协议——清晰、可靠、可审计。

在下一篇中,我们将探讨 OpenClaw 的部署模型演进:从单机 Docker 到 Kubernetes Operator。

下一篇预告:
第 18 篇:OpenClaw 架构下Skills System —— 为什么“文档即工具”是 OpenClaw 的扩展灵魂

您的 AI 助手,从此由您定义。若感兴趣可以浏览本书其他章节内容:

第 1 篇:OpenClaw 是什么?—— 工业级 AI 智能体网关的定位与愿景

第 2 篇:三位一体架构详解 —— 网关层、协议层、智能体系如何协同工作

第 3 篇:ACP 协议设计哲学 —— 为什么 OpenClaw 选择自研 Agent Client Protocol

第 4 篇:启动与配置体系 —— openclaw.mjs、config.yaml 与环境变量管理

第 5 篇:run.ts 上篇 —— 模型调度、账号轮询与上下文守护机制

第 6 篇:run.ts 下篇 —— 故障转移、重试策略与结果封装

第 7 篇:记忆系统基石 —— memory-search.ts 中的 RAG 配置解析与合并逻辑

第 8 篇:向量检索实战 —— OpenClaw 如何实现混合搜索(向量 + 全文)

第 9 篇:长期记忆与会话同步 —— 如何让 AI “记住”跨天对话

第 10 篇:exec.ts 上篇 —— 安全执行 Shell 命令的三层隔离模型

第 11 篇:exec.ts 下篇 —— 用户审批、后台任务与权限提升控制

第 12 篇:process.ts —— AI 如何像开发者一样管理后台进程

第 13 篇:安全边界设计 —— OpenClaw 如何防范 AI 滥用系统权限

第 14 篇:server-channels.ts —— 渠道插件生命周期管理器

第 15 篇:WhatsApp 深度集成 —— session.ts 与 Baileys 的健壮连接管理

第 16 篇:消息流入中枢 —— monitor-inbox.ts 如何解析、去重与防抖

第 17 篇:聊天 RPC 接口 —— chat.ts 中的历史查询、发送与中止逻辑

第 18 篇:Skills System —— 为什么“文档即工具”是 OpenClaw 的扩展灵魂

第 19 篇:可观测性工程 —— ws-log.ts 如何让 WebSocket 日志可读可用

第 20 篇:从零部署 OpenClaw —— 实战:接入 WhatsApp + 创建自定义 Skill

posted @ 2026-03-14 23:40  JackYang  阅读(1)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3