CliRunnerEngine
CliRunnerEngine 通过 ProcessSupervisor 管理外部 CLI Agent 工具(如 Claude Code CLI、Codex CLI)的子进程生命周期。支持普通子进程模式和 PTY 终端模式。
架构图
graph TB
Router["AgentRouter / MagiService"] --> Engine["CliRunnerEngine"]
Engine --> Resolve["resolveCliConfig()"]
Engine --> Backend["resolveCliBackendConfig()"]
Engine --> Args["buildCliArgs()"]
Engine --> Env["buildEnv()"]
Engine --> Supervisor["ProcessSupervisor"]
Supervisor --> Decision{"进程模式?"}
Decision -->|"mode: 'child'"| ChildAdapter["ChildAdapter<br/>child_process.spawn"]
Decision -->|"mode: 'pty'"| PtyAdapter["PtyAdapter<br/>node-pty"]
ChildAdapter --> RunRegistry["RunRegistry<br/>运行状态追踪"]
PtyAdapter --> RunRegistry
Engine --> Parse["parseCliOutput()"]
Engine --> DB["DB 持久化<br/>消息 + 会话 ID"]
Engine --> IPC["IPC 事件<br/>agent:event"]
ProcessSupervisor
ProcessSupervisor 是子进程生命周期管理器。
接口
interface ProcessSupervisor {
spawn(input: SpawnInput): ManagedRun;
cancel(runId: string, reason?: TerminationReason): void;
cancelScope(scopeKey: string, reason?: TerminationReason): void;
getRecord(runId: string): RunRecord | undefined;
resizePty(runId: string, cols: number, rows: number): boolean;
}
进程模式
Child 模式
interface SpawnChildInput {
mode: 'child';
argv: string[]; // 命令和参数
input?: string; // stdin 输入
stdinMode?: 'inherit' | 'pipe-open' | 'pipe-closed';
windowsVerbatimArguments?: boolean;
// ...共用字段
}
使用 child_process.spawn 创建子进程,标准输入/输出通过管道通信。
PTY 模式
interface SpawnPtyInput {
mode: 'pty';
shell?: string; // 默认: powershell.exe (Windows) / /bin/bash (Unix)
args?: string[];
cols?: number; // 默认 120
rows?: number; // 默认 30
// ...共用字段
}
使用 node-pty 创建伪终端,支持完整的终端交互(颜色、光标、行编辑)。
ManagedRun
interface ManagedRun {
runId: string;
pid?: number;
startedAtMs: number;
stdin?: ManagedRunStdin;
wait: () => Promise<RunExit>;
cancel: (reason?: TerminationReason) => void;
}
interface RunExit {
reason: TerminationReason;
exitCode: number | null;
exitSignal: string | null;
durationMs: number;
stdout: string;
stderr: string;
timedOut: boolean;
noOutputTimedOut: boolean;
}
终止原因
type TerminationReason =
| 'manual-cancel' // 用户手动取消
| 'overall-timeout' // 总时间超时
| 'no-output-timeout' // 无输出超时
| 'spawn-error' // 进程启动失败
| 'signal' // 收到信号
| 'exit'; // 正常退出
运行状态
type RunState = 'starting' | 'running' | 'exiting' | 'exited';
interface RunRecord {
runId: string;
sessionId: string;
backendId: string;
scopeKey?: string;
pid?: number;
startedAtMs: number;
lastOutputAtMs: number;
state: RunState;
terminationReason?: TerminationReason;
exitCode?: number | null;
}
Scope 管理
scopeKey 用于进程分组,同一 scope 内的进程可以批量取消:
scopeKey = `cli:${backendId}:${cliSessionId}`
replaceExistingScope = true // 新进程自动取消同 scope 的旧进程
超时处理
双超时机制:
- 总时间超时(
timeoutMs)— 进程运行总时间上限 - 无输出超时(
noOutputTimeoutMs)— 进程无输出的持续时间上限
graph LR
Start["进程启动"] --> Timer1["总时间计时器<br/>默认 300s"]
Start --> Timer2["无输出计时器<br/>动态计算"]
Timer1 -->|超时| Kill1["终止: overall-timeout"]
Timer2 -->|超时| Kill2["终止: no-output-timeout"]
Output["收到输出"] -->|重置| Timer2
无输出超时计算(cli-watchdog):
noOutputTimeoutMs = clamp(
overallTimeoutMs * ratio,
minMs,
maxMs,
)
| 参数 | Fresh(首次) | Resume(继续) |
|---|---|---|
| ratio | 0.8 | 0.3 |
| minMs | 180,000 (3min) | 60,000 (1min) |
| maxMs | 600,000 (10min) | 180,000 (3min) |
跨平台进程终止
kill-tree.ts 负责跨平台的进程树终止:
- Windows —
taskkill /F /T /PID(强制终止进程树) - Unix — 进程组信号:
SIGTERM→ 等待 →SIGKILL
CLI 后端
内置后端
Claude Code CLI
{
command: 'claude',
args: ['--output-format', 'json', '--verbose', '--max-turns', '25'],
resumeArgs: ['--output-format', 'json', '--verbose', '--resume', '{sessionId}'],
output: 'json',
input: 'arg',
modelArg: '--model',
sessionArg: '--session-id',
sessionMode: 'always',
}
Codex CLI
{
command: 'codex',
args: ['exec', '--json', '--color', 'never', '--sandbox', 'workspace-write', '--skip-git-repo-check'],
output: 'jsonl',
input: 'arg',
modelArg: '--model',
sessionMode: 'none',
}
后端配置解析
function resolveCliBackendConfig(
backendId: string,
overrides?: Partial<CliBackendConfig>,
): ResolvedCliBackend | null;
- 根据
backendId查找内置配置 - 应用用户覆盖配置
- 返回合并后的配置
CLI 参数构建
function buildCliArgs(options: {
backend: CliBackendConfig;
baseArgs: string[];
modelId?: string;
sessionId?: string;
systemPrompt?: string;
isResume: boolean;
}): string[];
依次拼接:baseArgs → --model → --session-id → --append-system-prompt
输出解析
function parseCliOutput(
stdout: string,
outputMode: 'json' | 'jsonl' | 'text',
sessionIdFields?: string[],
): { text: string; sessionId?: string; usage?: object };
| 输出模式 | 解析方式 |
|---|---|
json | 解析整个输出为 JSON,提取 result 或 text 字段 |
jsonl | 逐行解析 JSON Lines,拼接内容 |
text | 直接使用原始文本 |
会话管理
CLI Session ID
CliRunnerEngine 为支持多轮对话的 CLI 工具维护 session ID:
sessionMode: 'always' | 'existing' | 'none'
| 模式 | 行为 |
|---|---|
always | 始终使用 session ID(没有则生成新的) |
existing | 仅使用已有的 session ID |
none | 不使用 session ID |
Session ID 存储在 chatSessions.cliSessionIds 字段中(按 backendId 分组)。
PTY 终端
PTY 模式下,终端数据实时流式发送给所有渲染进程窗口:
| IPC 事件 | 载荷 | 说明 |
|---|---|---|
cli:ptyData | { sessionId, data } | 终端输出数据 |
cli:ptyExit | { sessionId, exitCode, reason } | 终端进程退出 |
支持终端大小调整:
resizePty(dbSessionId: string, cols: number, rows: number): boolean;
IPC 事件
| 事件 | 说明 |
|---|---|
agent:event type=userMessage | 用户消息已保存 |
agent:event type=processing | 正在执行 CLI 命令 |
agent:event type=assistantMessage | CLI 输出已保存 |
agent:event type=result | 执行结果统计 |
agent:event type=error | 错误信息 |
cli:ptyData | PTY 终端数据流 |
cli:ptyExit | PTY 进程退出 |
关键文件
| 文件 | 路径 | 说明 |
|---|---|---|
| CliRunnerEngine | agent-core/engine/cli/CliRunnerEngine.ts | IEngine 实现 |
| cli-backends | agent-core/engine/cli/cli-backends.ts | 内置后端配置和解析 |
| cli-helpers | agent-core/engine/cli/cli-helpers.ts | 参数构建和输出解析 |
| cli-watchdog | agent-core/engine/cli/cli-watchdog.ts | 无输出超时计算 |
| cli/index | agent-core/engine/cli/index.ts | 模块导出 |
| supervisor | process/supervisor/supervisor.ts | ProcessSupervisor 实现 |
| child-adapter | process/supervisor/child-adapter.ts | 子进程适配器 |
| pty-adapter | process/supervisor/pty-adapter.ts | PTY 适配器 |
| kill-tree | process/supervisor/kill-tree.ts | 跨平台进程终止 |
| run-registry | process/supervisor/run-registry.ts | 运行状态注册表 |
| types | process/supervisor/types.ts | 类型定义 |
| CliEngineConfig | @shared/contracts/cli-types.ts | 共享类型 |
所有路径相对于 packages/desktop/app/main/services/。
扩展点
- 新增 CLI 后端:在
cli-backends.ts中添加新的CliBackendConfig - 自定义输出解析:在
cli-helpers.ts的parseCliOutput中添加新格式 - 自定义超时策略:修改
CLI_FRESH_WATCHDOG_DEFAULTS/CLI_RESUME_WATCHDOG_DEFAULTS
相关模块
| 模块 | 路径 | 关系 |
|---|---|---|
| EngineDispatcher | agent-core/engine/EngineDispatcher.ts | 引擎注册 |
| ProcessSupervisor | process/supervisor/ | 子进程管理 |
| MagiService | agent-core/magi/MagiService.ts | 高层编排 |