7.4 超时与中止机制

生成模型:Claude Opus 4.6 (anthropic/claude-opus-4-6) Token 消耗:输入 ~350,000 tokens,输出 ~28,000 tokens(本章合计)


LLM 调用天生具有不确定性——模型可能因速率限制而挂起,复杂的工具调用链可能无限循环,或者用户可能改变主意想取消正在进行的操作。OpenClaw 通过多层超时和中止机制来应对这些场景,确保系统始终保持响应能力。

7.4.1 Agent 运行超时(默认 600 秒)

每次 Agent 运行都有一个最大执行时间限制。超过这个时间,运行会被强制终止。

超时解析

// src/agents/timeout.ts
const DEFAULT_AGENT_TIMEOUT_SECONDS = 600; // 10 分钟

export function resolveAgentTimeoutSeconds(cfg?: OpenClawConfig): number {
  const raw = normalizeNumber(cfg?.agents?.defaults?.timeoutSeconds);
  const seconds = raw ?? DEFAULT_AGENT_TIMEOUT_SECONDS;
  return Math.max(seconds, 1);  // 最小 1 秒
}

export function resolveAgentTimeoutMs(opts: {
  cfg?: OpenClawConfig;
  overrideMs?: number | null;
  overrideSeconds?: number | null;
  minMs?: number;
}): number {
  const minMs = Math.max(normalizeNumber(opts.minMs) ?? 1, 1);
  const defaultMs = resolveAgentTimeoutSeconds(opts.cfg) * 1000;

  // 特殊值:0 表示"无超时"(实际设为 30 天)
  const NO_TIMEOUT_MS = 30 * 24 * 60 * 60 * 1000;
  const overrideSeconds = normalizeNumber(opts.overrideSeconds);
  if (overrideSeconds === 0) return NO_TIMEOUT_MS;
  if (overrideSeconds && overrideSeconds > 0) {
    return Math.max(overrideSeconds * 1000, minMs);
  }
  return Math.max(defaultMs, minMs);
}

超时配置有三个来源,优先级从高到低:

来源
配置路径
说明

请求参数

agent RPC 的 timeout 字段

单次调用的超时覆盖

全局配置

agents.defaults.timeoutSeconds

所有 Agent 的默认超时

硬编码默认

600 秒

无配置时的回退值

将超时设为 0 是一个特殊值,表示"实际上不超时"。由于 JavaScript 的 setTimeout 不支持 Infinity,系统使用 30 天(约 260 万秒)作为替代。

7.4.2 agent.wait 等待超时(默认 30 秒)

agent.wait 是 Gateway 提供的一个 RPC 方法,允许调用方阻塞等待某个 Agent 运行完成。它主要被子代理注册表用来等待子代理的完成。

等待机制的内部实现

waitForAgentJob 使用了一个基于事件监听的等待模式:

这个实现有几个值得注意的设计决策:

  1. 先查缓存再等待——避免竞态条件:如果运行在创建 Promise 之前就已完成,缓存查询可以立即返回

  2. settled 标志保证单次 resolve——finish 函数只会执行一次,无论是事件触发还是超时触发

  3. 自动清理——无论哪个路径触发了 resolve,都会同时清理定时器和事件订阅

运行快照缓存

完成的 Agent 运行会在缓存中保留 10 分钟:

10 分钟的 TTL 保证了:即使 agent.wait 调用略晚于 Agent 运行完成,它仍能从缓存中获取结果,而不会因为错过事件而永久阻塞。

7.4.3 AbortSignal 取消链

JavaScript 的 AbortSignal / AbortController 机制为 OpenClaw 提供了一种贯穿整个调用栈的协作式取消方案。

衍生解释AbortControllerAbortSignal 是 Web API 中的标准机制,已被 Node.js 完整实现。AbortController 创建一个控制器,它持有一个 AbortSignal 对象。当调用 controller.abort() 时,signal 会触发 abort 事件,所有监听该 signal 的操作都会被通知取消。这类似于 Go 语言中的 context.Context 或 C# 中的 CancellationToken

取消链的传播路径

runEmbeddedPiAgent 中,abortSignal 作为参数传递到每一层:

当 signal 被触发时,正在运行的 LLM 请求会被取消(通过 fetch 的 signal 支持),工具执行会被中断,整个 Agent 循环会在下一个检查点退出。

活跃运行中止

每个活跃的 Agent 运行都注册了一个中止句柄:

abort() 调用会:

  1. 触发 AbortSignal 的 abort 事件

  2. 取消正在进行的 HTTP 请求

  3. 将运行标记为 aborted(但不立即删除——等清理完成后删除)

7.4.4 聊天中止(src/gateway/chat-abort.ts

Gateway 层面的中止管理比单个 Agent 运行更复杂,因为它需要处理多运行、跨连接的场景。

中止控制器注册

每个 Agent 运行在 Gateway 中都有一个对应的中止控制器条目:

expiresAtMs 是一个安全网——即使中止逻辑出错导致运行没有被正常清理,过期时间也会保证资源最终被回收。过期时间的计算考虑了运行超时加上一个宽限期:

停止命令检测

用户可以通过发送 /stop 命令来中止当前正在运行的 Agent:

除了 /stop 外,isAbortTrigger 还支持其他触发模式(如自定义配置的中止关键词)。

按运行 ID 中止

中止操作的四个步骤是原子性的——一旦开始中止,所有相关状态都会被清理,中止事件会被广播给所有连接的客户端。

按会话键批量中止

当用户发送 /stop 时,需要中止该会话中所有正在运行的 Agent:

这个函数遍历所有活跃的中止控制器,找到属于目标会话的所有运行并逐一中止。

中止事件广播

中止操作会通过 WebSocket 广播一个 chat 事件,通知所有客户端:

客户端收到中止事件后可以:

  • 停止显示"正在输入..."指示器

  • 在消息气泡上标记"已取消"

  • 清理流式输出缓冲区

多层超时与中止的完整图景

下表总结了 OpenClaw 中所有的超时和中止机制:

层级
机制
默认值
触发方式

LLM 请求

HTTP 请求超时

由提供者 SDK 控制

网络超时自动触发

Agent 运行

resolveAgentTimeoutMs

600 秒

计时器自动触发

Agent 等待

agent.wait

30 秒

等待超时返回 null

用户中止

/stop 命令

用户手动触发

资源清理

expiresAtMs

超时 + 60 秒宽限

过期自动清理

AbortSignal

协作式取消

由上层触发传播

这些机制层层嵌套,形成了一个完整的安全网——即使某一层的超时/中止失败,上层机制也会最终保证资源被回收。


本节小结

  1. Agent 运行超时默认 600 秒,可通过配置文件或请求参数覆盖。特殊值 0 表示不超时(实际为 30 天)。

  2. agent.wait RPC 方法允许调用方阻塞等待 Agent 运行完成,使用事件监听 + 超时的 Promise 模式实现,运行快照缓存 10 分钟防止竞态条件。

  3. AbortSignal 取消链贯穿整个调用栈,从 Gateway 到 LLM HTTP 请求,实现了协作式取消。

  4. Gateway 级中止管理包括按运行 ID 中止和按会话批量中止,中止操作原子性地清理所有相关状态并广播事件通知客户端。

  5. 多层安全网确保即使某一层机制失败,系统也不会泄漏资源——过期时间机制是最后的保障。

Last updated