CliRunnerEngine
CliRunnerEngine は ProcessSupervisor を通じて、外部 CLI エージェントツール(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{"Process mode?"}
Decision -->|"mode: 'child'"| ChildAdapter["ChildAdapter<br/>child_process.spawn"]
Decision -->|"mode: 'pty'"| PtyAdapter["PtyAdapter<br/>node-pty"]
ChildAdapter --> RunRegistry["RunRegistry<br/>run state tracking"]
PtyAdapter --> RunRegistry
Engine --> Parse["parseCliOutput()"]
Engine --> DB["DB persistence<br/>messages + session ID"]
Engine --> IPC["IPC events<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[]; // command and arguments
input?: string; // stdin input
stdinMode?: 'inherit' | 'pipe-open' | 'pipe-closed';
windowsVerbatimArguments?: boolean;
// ...shared fields
}
child_process.spawn を使用してサブプロセスを作成し、パイプ経由で stdin/stdout 通信を行います。
PTY モード
interface SpawnPtyInput {
mode: 'pty';
shell?: string; // default: powershell.exe (Windows) / /bin/bash (Unix)
args?: string[];
cols?: number; // default 120
rows?: number; // default 30
// ...shared fields
}
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' // user manual cancel
| 'overall-timeout' // total time timeout
| 'no-output-timeout' // no output timeout
| 'spawn-error' // spawn failure
| 'signal' // received signal
| 'exit'; // normal 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;
}
スコープ管理
scopeKey はプロセスをグループ化します。同じスコープ内のプロセスはまとめてキャンセルできます。
scopeKey = `cli:${backendId}:${cliSessionId}`
replaceExistingScope = true // new process auto-cancels old ones in same scope
タイムアウト処理
デュアルタイムアウト機構:
- 全体タイムアウト (
timeoutMs) — プロセスの合計実行時間の上限 - 無出力タイムアウト (
noOutputTimeoutMs) — プロセスのアイドル時間の上限
graph LR
Start["Process start"] --> Timer1["Overall timeout timer<br/>default 300s"]
Start --> Timer2["No-output timer<br/>dynamic compute"]
Timer1 -->|timeout| Kill1["terminate: overall-timeout"]
Timer2 -->|timeout| Kill2["terminate: no-output-timeout"]
Output["output received"] -->|reset| Timer2
無出力タイムアウトの計算方法 (cli-watchdog):
noOutputTimeoutMs = clamp(
overallTimeoutMs * ratio,
minMs,
maxMs,
)
| パラメータ | Fresh(初回) | Resume(継続) |
|---|---|---|
| ratio | 0.8 | 0.3 |
| minMs | 180,000(3分) | 60,000(1分) |
| maxMs | 600,000(10分) | 180,000(3分) |
クロスプラットフォームのプロセス終了
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 行をパースし、コンテンツを連結する |
text | 生テキストをそのまま使用する |
セッション管理
CLI セッション ID
CliRunnerEngine は、マルチターン会話をサポートする CLI ツール向けにセッション ID を維持します。
sessionMode: 'always' | 'existing' | 'none'
| モード | 動作 |
|---|---|
always | 常にセッション ID を使用する(未設定の場合は新規生成) |
existing | 既存のセッション ID のみを使用する |
none | セッション ID を使用しない |
セッション 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 | 高レベルオーケストレーション |