Skip to content

超时与资源控制 ​

本文引用的源码文件

本文引用的文件

目录 ​

  1. 引言
  2. 项目结构
  3. 核心组件
  4. 架构总览
  5. 详细组件分析
  6. 依赖关系分析
  7. 性能考虑
  8. 故障排查指南
  9. 结论
  10. 附录:配置参数速查

引言 ​

本文聚焦“超时与资源控制”子系统,围绕命令执行生命周期中的智能超时策略、总预算封顶算法、输出心跳检测、最大延长次数限制、内存与 I/O 保护、进程树终止以及流式空闲超时等主题展开。文档面向扩展端(Chrome MV3)与 Agent 端(Node.js)双进程协作场景,帮助读者理解:

  • 为什么静默命令会被快速杀死;
  • 活跃命令如何渐进延长并受 600 秒总预算封顶;
  • 何时触发 kill 或 extend;
  • 如何通过配置调优不同工具的超时与并发行为;
  • 如何在出现卡流、僵尸进程、磁盘写爆等问题时定位与修复。

项目结构 ​

本主题涉及的关键代码分布在以下位置:

  • Agent 端命令执行器与超时决策:agent/src/executor.js
  • Agent 端命令超时策略单元测试:agent/test/unit/executor-timeout-policy.unit.test.js
  • 扩展端流式控制器与空闲超时:src/background/stream-controller.js
  • 扩展端工具定义与超时描述:src/background/tools/agent-tools.js
  • 扩展端 ReAct/工具超时配置读取:src/background/config.js
  • 扩展端全局常量(工具超时默认值):src/background/constants.js
  • Agent 端配置默认值(含 commandTimeout):agent/src/config.js
  • Agent 端进程树终止工具:agent/src/process-tree.js
graph TB
subgraph "扩展端"
BG["background/config.js<br/>读取 reactToolTimeout / streamChunkDelay"]
CONST["background/constants.js<br/>TOOL_TIMEOUT_MS 默认值"]
TOOLS["background/tools/agent-tools.js<br/>工具 schema 中 timeoutMs 说明"]
STREAM["background/stream-controller.js<br/>SSE 解析 + 空闲超时"]
end
subgraph "Agent 端"
EXEC["src/executor.js<br/>命令执行 + 智能超时 + 输出缓冲 + 进程终止"]
TEST["test/unit/executor-timeout-policy.unit.test.js<br/>超时策略单测"]
ACFG["src/config.js<br/>commandTimeout 默认值"]
PTREE["src/process-tree.js<br/>进程树终止工具"]
end
BG --> TOOLS
CONST --> BG
TOOLS --> EXEC
STREAM --> BG
EXEC --> PTREE
EXEC --> TEST
EXEC --> ACFG

图表来源

章节来源

核心组件 ​

本节从“谁负责什么”的角度梳理关键职责:

  • Agent 命令执行器 executor.js:负责子进程创建、标准输出/错误流处理、输出缓冲上限、WebSocket 广播、超时定时器、进程终止、运行态缓存。
  • 超时决策函数 decideTimeoutAction:纯函数,依据“是否在本预算期内有输出”“已延长次数”“已用时间”“基础超时”计算 extend 或 kill,并给出下一期剩余预算。
  • 扩展端流式控制器 stream-controller.js:负责 SSE 数据解析、tool_calls 累积、chunk 节流发送、流空闲超时(无数据即中断)。
  • 配置层:
    • 扩展端 background/config.js 读取 reactToolTimeout、streamChunkDelay 等;
    • 扩展端 constants.js 提供 TOOL_TIMEOUT_MS 默认值;
    • Agent 端 config.js 提供 commandTimeout 默认值。
  • 进程树终止 process-tree.js:跨平台递归终止子进程及其孙进程,避免孤儿进程继续写盘。

章节来源

架构总览 ​

下图展示一次命令执行的端到端流程,包括扩展端发起调用、Agent 端执行命令、输出心跳驱动延长、总预算封顶与最终终止。

sequenceDiagram
participant Ext as "扩展端"
participant Tools as "agent-tools.js"
participant Exec as "executor.js"
participant Proc as "子进程"
participant WS as "WebSocket 客户端"
Ext->>Tools : 调用 agent_execute_command(timeoutMs?)
Tools->>Exec : executeCommand(command, execOpts.timeoutMs)
Exec->>Proc : spawn(shell, args, env)
Exec->>Exec : armTimeout(初始超时)
Proc-->>Exec : stdout/stderr 数据
Exec->>Exec : outputInPeriod = true
Exec->>WS : 广播 stdout/stderr
Note over Exec : 每次收到输出视为心跳
Exec->>Exec : 到期 → decideTimeoutAction(...)
alt 静默命令或已达延长上限或触及总预算封顶
Exec->>Exec : action=kill
Exec->>Proc : killProcess()
Exec->>WS : exit(killed=true,timedOut=true)
else 期内有输出且可延长
Exec->>Exec : action=extend
Exec->>Exec : 重置 outputInPeriod=false
Exec->>Exec : armTimeout(nextTimeoutMs)
end

图表来源

详细组件分析 ​

智能超时策略与状态机 ​

智能超时的核心是“以输出为心跳”的延长机制:

  • 基础超时:由扩展端传入的 timeoutMs 或 Agent 端配置的 commandTimeout 决定。
  • 动态延长:若当前预算期内收到 stdout/stderr 任一输出,则视为活跃,允许延长一期。
  • 静默失败检测:若当前预算期内无任何输出,直接 kill,不放大等待窗口。
  • 最大延长次数:MAX_TIMEOUT_EXTENSIONS 次后即使活跃也终止。
  • 总预算封顶:TOTAL_BUDGET_CAP_MS 为 600 秒,防止无限延长。
flowchart TD
Start(["进入超时裁决"]) --> CheckExt["检查 extensions >= MAX_TIMEOUT_EXTENSIONS?"]
CheckExt --> |是| Kill1["返回 kill"]
CheckExt --> |否| CheckSilent["检查 outputInPeriod == false?"]
CheckSilent --> |是| Kill2["返回 kill静默即杀"]
CheckSilent --> |否| CalcBudget["currentBudget = min(base*(1+ext), cap)"]
CalcBudget --> CalcNext["nextBudget = min(base*(2+ext), cap)"]
CalcNext --> CanGrow{"nextBudget > currentBudget?"}
CanGrow --> |否| Kill3["返回 kill触及封顶"]
CanGrow --> |是| Extend["返回 extend<br/>extensions+1<br/>nextTimeoutMs = nextBudget - elapsed"]

图表来源

章节来源

关键实现要点 ​

  • 输出心跳:stdout/stderr 的 data 事件都会将 outputInPeriod 置为 true,同时写入缓冲区并广播给 WebSocket 客户端。
  • 受管模式:当调用方显式传入 timeoutMs 时,使用策略函数裁决延长;未传时使用旧语义(到期直接杀)。
  • 同步执行兜底:executeCommandSync 在内部调用 executeCommand 的基础上增加额外 5 秒兜底超时,确保即使内层策略失效也能回收输出并返回错误。

章节来源

总预算封顶算法 ​

总预算封顶的核心约束是 600 秒(TOTAL_BUDGET_CAP_MS),用于防止长时间运行的活跃命令无限延长。算法要点:

  • 每期的“当前预算”为 baseTimeout × (1 + extensions),但不超过 cap。
  • 下一期的“下一预算”为 baseTimeout × (2 + extensions),同样不超过 cap。
  • 如果下一预算无法增长(<= 当前预算),则不再延长,直接 kill。
  • 测试覆盖了达到封顶、边界截断等情形。
flowchart TD
A["输入:outputInPeriod, extensions, elapsedMs, baseTimeout"] --> B["currentBudget = min(base*(1+ext), cap)"]
B --> C["nextBudget = min(base*(2+ext), cap)"]
C --> D{"nextBudget <= currentBudget ?"}
D --> |是| E["kill封顶"]
D --> |否| F["extendextensions+1, nextTimeoutMs=nextBudget-elapsed"]

图表来源

章节来源

输出心跳与缓冲区保护 ​

  • 心跳检测:stdout/stderr 的 data 事件会设置 outputInPeriod=true,作为“活跃”信号。
  • 输出缓冲上限:每个流的缓冲区上限为 5MB,超过时会截断并广播 truncated 消息,防止长时间命令导致内存无限增长。
  • 已完成进程回放:completedProcesses 缓存最近完成的进程输出,供延迟连接的 WebSocket 客户端回放。
classDiagram
class ExecutorEntry {
+string execId
+ChildProcess process
+Set wsClients
+number timeoutId
+number forceKillId
+string stdoutBuf
+string stderrBuf
+boolean truncStdout
+boolean truncStderr
+string command
+number startTime
+number baseTimeout
+boolean managed
+number extensions
+boolean outputInPeriod
+boolean detached
+boolean timedOut
}
class CompletedCache {
+string stdoutBuf
+string stderrBuf
+number exitCode
+boolean killed
+boolean timedOut
+string error
+number cleanupTimeout
}
ExecutorEntry --> CompletedCache : "完成后迁移到缓存"

图表来源

章节来源

资源限制机制 ​

  • 内存使用监控:通过输出缓冲区上限(5MB)限制 stdout/stderr 的内存占用;completedProcesses 缓存带清理定时器,避免长期驻留。
  • CPU 占用控制:未在代码中直接限制 CPU,但通过超时与进程终止间接控制长任务对 CPU 的占用。
  • 并发执行限制:runningProcesses Map 维护运行中进程;disconnectWsClient 在旧语义下所有客户端断开可取消超时,但在受管模式下不会因连接断开而解除超时权威。
  • 磁盘 I/O 保护:通过安全环境变量白名单减少敏感信息泄露;killProcess 与 process-tree.js 提供跨平台进程组/进程树终止,避免孤儿进程持续写盘。
flowchart TD
IO["命令输出"] --> BUF["输出缓冲上限 5MB"]
BUF --> TRUNC{"超过上限?"}
TRUNC --> |是| MARK["标记 truncated 并广播提示"]
TRUNC --> |否| KEEP["追加到缓冲区"]
MARK --> CLEAN["完成时清理 completedProcesses 缓存"]
KEEP --> CLEAN

图表来源

章节来源

超时决策函数与边界条件 ​

  • extend/kill 判断:
    • 已达到最大延长次数 → kill
    • 本期无输出 → kill(静默即杀)
    • 下一预算无法增长(触及封顶)→ kill
    • 否则 → extend,并设置下一期剩余预算
  • 状态机转换:
    • 每次超时到期调用 decideTimeoutAction,根据结果重新 armTimeout 或直接 killProcess。
  • 边界条件:
    • 同步执行 executeCommandSync 增加额外 5 秒兜底,确保即使内层策略失效也能回收输出并返回错误。
    • disconnectWsClient 在受管模式下不因连接断开而解除超时,避免插件端 SW 被 Chrome 杀掉后误判用户意图。
stateDiagram-v2
[*] --> 运行中
运行中 --> 延长 : "期内有输出且可增长"
运行中 --> 终止 : "静默/达延长上限/触及封顶"
延长 --> 运行中 : "armTimeout(nextTimeoutMs)"
终止 --> [*]

图表来源

章节来源

流式空闲超时与前端交互 ​

扩展端的流式控制器负责 SSE 解析与 chunk 节流,并提供“流空闲超时”保护:

  • 空闲超时:在 readSSEStream 中,每次 reader.read() 前创建空闲计时器,若持续一段时间无数据则判定卡住并中断。
  • tool_calls 检测:在 finishReason === 'tool_calls' 时立即通知前端,避免极快工具的执行中状态不可见。
  • chunk 节流:sendChunk 基于 streamChunkDelay 合并发送,降低 UI 刷新压力。
sequenceDiagram
participant API as "上游 API"
participant SC as "StreamController"
participant SP as "Side Panel"
API-->>SC : SSE chunks
SC->>SC : feedLine() 解析
SC->>SP : sendStart()
loop 每条数据
SC->>SC : 提取 usage/tool_calls/content
SC->>SP : sendChunk(delta)
end
SC->>SP : STREAM_DONE(finalContent, usage, toolCalls)

图表来源

章节来源

依赖关系分析 ​

  • executor.js 依赖:
    • child_process.spawn 创建子进程;
    • fs.existsSync 探测 shell 路径;
    • os.platform/os.env 选择 shell;
    • i18n.translate 输出国际化错误;
    • config.loadConfig 获取 commandTimeout 与工作目录。
  • stream-controller.js 依赖:
    • chrome.runtime.sendMessage 向前端发送流式消息;
    • logger 记录调试信息。
  • 配置依赖:
    • 扩展端 background/config.js 读取 reactToolTimeout、streamChunkDelay 等;
    • Agent 端 config.js 提供 commandTimeout 默认值。
graph LR
EXEC["executor.js"] --> CHILD["child_process"]
EXEC --> FS["fs"]
EXEC --> OS["os"]
EXEC --> I18N["i18n"]
EXEC --> CFG["config.js"]
STREAM["stream-controller.js"] --> CHROME["chrome.runtime"]
STREAM --> LOG["logger"]
BGCFG["background/config.js"] --> STORAGE["chrome.storage.local"]

图表来源

章节来源

性能考虑 ​

  • 输出缓冲上限:5MB 限制防止长时间命令导致内存膨胀;超过上限后截断并广播 truncated 提示,避免 UI 卡顿。
  • 流式节流:streamChunkDelay 控制 chunk 发送频率,减少频繁 DOM 更新带来的渲染压力。
  • 空闲超时:readSSEStream 的空闲超时保护能及时发现卡流并释放底层 reader,避免连接泄漏。
  • 进程终止:killProcess 与 process-tree.js 提供跨平台递归终止,避免孤儿进程继续消耗 CPU/磁盘。
  • 配置缓存:Agent 端 config.js 使用内存缓存与 mtime 校验,减少频繁磁盘读取。

[本节为通用性能建议,不直接分析具体文件]

故障排查指南 ​

  • 命令被快速杀死:
    • 检查是否在当期预算内产生过 stdout/stderr 输出;若无输出,静默即杀。
    • 检查是否已达到最大延长次数(MAX_TIMEOUT_EXTENSIONS)。
    • 检查是否触及总预算封顶(600 秒)。
  • 命令长时间运行但未结束:
    • 检查 executeCommandSync 的额外 5 秒兜底是否生效;确认 killProcess 是否成功发出 SIGTERM/SIGKILL。
    • 检查 disconnectWsClient 是否误解除超时(受管模式下不应因连接断开而解除)。
  • 流式输出卡住:
    • 检查 StreamController 的空闲超时是否触发;查看日志中是否有 StreamIdleTimeout 警告。
    • 确认上游 API 是否持续返回 SSE 数据;必要时调整 streamChunkDelay。
  • 磁盘写爆或僵尸进程:
    • 检查 killProcess 与 process-tree.js 是否正确递归终止进程树。
    • 检查输出缓冲是否被截断(truncated 提示),必要时优化命令输出量。

章节来源

结论 ​

本系统的超时与资源控制以“输出心跳 + 渐进延长 + 总预算封顶”为核心,结合输出缓冲上限、流式空闲超时与跨平台进程树终止,形成一套稳健的命令执行保护机制。通过扩展端与 Agent 端的配置协同,可在保证用户体验的同时,有效防止资源滥用与系统不稳定。

[本节为总结性内容,不直接分析具体文件]

附录:配置参数速查 ​

  • 扩展端配置(background/config.js):
    • reactToolTimeout:工具级超时(毫秒),默认来自 constants.js 的 TOOL_TIMEOUT_MS。
    • streamChunkDelay:流式 chunk 节流间隔(毫秒)。
    • agentStreamEnabled:是否启用 Agent 命令流式输出。
  • Agent 端配置(agent/src/config.js):
    • commandTimeout:命令默认超时(毫秒),默认 300000ms。
  • 工具定义(background/tools/agent-tools.js):
    • timeoutMs:工具调用时可传入的执行时限(毫秒),最小 5000;静默命令接近该限制会被杀死,活跃命令最多延长至 10 分钟总预算。

章节来源