主题
超时与资源控制
本文引用的源码文件
本文引用的文件
目录
引言
本文聚焦“超时与资源控制”子系统,围绕命令执行生命周期中的智能超时策略、总预算封顶算法、输出心跳检测、最大延长次数限制、内存与 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
图表来源
- src/background/config.js#L10-L60
- src/background/constants.js#L151-L170
- src/background/tools/agent-tools.js#L67-L71
- src/background/stream-controller.js#L6-L9
- agent/src/executor.js#L165-L186
- agent/src/config.js#L15-L22
- agent/src/process-tree.js#L1-L42
章节来源
- src/background/config.js#L10-L60
- src/background/constants.js#L151-L170
- src/background/tools/agent-tools.js#L67-L71
- src/background/stream-controller.js#L6-L9
- agent/src/executor.js#L165-L186
- agent/src/config.js#L15-L22
- agent/src/process-tree.js#L1-L42
核心组件
本节从“谁负责什么”的角度梳理关键职责:
- 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/src/executor.js#L93-L114
- agent/src/executor.js#L165-L186
- src/background/stream-controller.js#L36-L53
- src/background/config.js#L10-L60
- src/background/constants.js#L151-L170
- agent/src/config.js#L15-L22
- agent/src/process-tree.js#L1-L42
架构总览
下图展示一次命令执行的端到端流程,包括扩展端发起调用、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
图表来源
- src/background/tools/agent-tools.js#L67-L71
- agent/src/executor.js#L198-L275
- agent/src/executor.js#L298-L323
- agent/src/executor.js#L326-L383
- agent/src/executor.js#L500-L549
详细组件分析
智能超时策略与状态机
智能超时的核心是“以输出为心跳”的延长机制:
- 基础超时:由扩展端传入的 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 : "完成后迁移到缓存"
图表来源
- agent/src/executor.js#L93-L114
- agent/src/executor.js#L240-L246
- agent/src/executor.js#L332-L344
- agent/src/executor.js#L429-L453
章节来源
- agent/src/executor.js#L93-L114
- agent/src/executor.js#L298-L323
- agent/src/executor.js#L332-L344
- agent/src/executor.js#L429-L453
资源限制机制
- 内存使用监控:通过输出缓冲区上限(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
图表来源
章节来源
- agent/src/executor.js#L93-L114
- agent/src/executor.js#L298-L323
- agent/src/executor.js#L332-L344
- agent/src/process-tree.js#L1-L42
超时决策函数与边界条件
- 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)
图表来源
- src/background/stream-controller.js#L36-L53
- src/background/stream-controller.js#L59-L168
- src/background/stream-controller.js#L170-L252
- src/background/stream-controller.js#L287-L409
章节来源
- src/background/stream-controller.js#L6-L9
- src/background/stream-controller.js#L59-L168
- src/background/stream-controller.js#L170-L252
- src/background/stream-controller.js#L287-L409
依赖关系分析
- 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"]
图表来源
- agent/src/executor.js#L1-L8
- agent/src/executor.js#L6
- src/background/stream-controller.js#L1
- src/background/config.js#L10-L60
章节来源
- agent/src/executor.js#L1-L8
- agent/src/executor.js#L6
- src/background/stream-controller.js#L1
- src/background/config.js#L10-L60
性能考虑
- 输出缓冲上限: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/src/executor.js#L248-L275
- agent/src/executor.js#L392-L423
- agent/src/executor.js#L459-L479
- src/background/stream-controller.js#L287-L409
- agent/src/process-tree.js#L1-L42
结论
本系统的超时与资源控制以“输出心跳 + 渐进延长 + 总预算封顶”为核心,结合输出缓冲上限、流式空闲超时与跨平台进程树终止,形成一套稳健的命令执行保护机制。通过扩展端与 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 分钟总预算。
章节来源