メインコンテンツまでスキップ

CliRunnerEngine

CliRunnerEngineProcessSupervisor を通じて、外部 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

タイムアウト処理

デュアルタイムアウト機構:

  1. 全体タイムアウト (timeoutMs) — プロセスの合計実行時間の上限
  2. 無出力タイムアウト (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(継続)
ratio0.80.3
minMs180,000(3分)60,000(1分)
maxMs600,000(10分)180,000(3分)

クロスプラットフォームのプロセス終了

kill-tree.ts はクロスプラットフォームのプロセスツリー終了を処理します。

  • Windowstaskkill /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;
  1. backendId で組み込み設定を検索する
  2. ユーザーのオーバーライド設定を適用する
  3. マージされた設定を返す

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 フィールドを抽出する
jsonlJSON 行をパースし、コンテンツを連結する
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=processingCLI コマンドを実行中
agent:event type=assistantMessageCLI 出力が保存された
agent:event type=result実行結果の統計
agent:event type=errorエラーメッセージ
cli:ptyDataPTY ターミナルデータストリーム
cli:ptyExitPTY プロセスの終了

主要ファイル

ファイルパス説明
CliRunnerEngineagent-core/engine/cli/CliRunnerEngine.tsIEngine 実装
cli-backendsagent-core/engine/cli/cli-backends.ts組み込みバックエンド設定と解決ロジック
cli-helpersagent-core/engine/cli/cli-helpers.ts引数構築と出力パース
cli-watchdogagent-core/engine/cli/cli-watchdog.ts無出力タイムアウト計算
cli/indexagent-core/engine/cli/index.tsモジュールエクスポート
supervisorprocess/supervisor/supervisor.tsProcessSupervisor 実装
child-adapterprocess/supervisor/child-adapter.ts子プロセスアダプター
pty-adapterprocess/supervisor/pty-adapter.tsPTY アダプター
kill-treeprocess/supervisor/kill-tree.tsクロスプラットフォームのプロセス終了
run-registryprocess/supervisor/run-registry.ts実行状態レジストリ
typesprocess/supervisor/types.ts型定義
CliEngineConfig@shared/contracts/cli-types.ts共有型

すべてのパスは packages/desktop/app/main/services/ を基準とした相対パスです。

拡張ポイント

  • 新しい CLI バックエンド: cli-backends.ts に新しい CliBackendConfig を追加する
  • カスタム出力パース: cli-helpers.tsparseCliOutput に新しいフォーマットを追加する
  • カスタムタイムアウト戦略: CLI_FRESH_WATCHDOG_DEFAULTS / CLI_RESUME_WATCHDOG_DEFAULTS を変更する

関連モジュール

モジュールパス関係
EngineDispatcheragent-core/engine/EngineDispatcher.tsエンジン登録
ProcessSupervisorprocess/supervisor/サブプロセス管理
MagiServiceagent-core/magi/MagiService.ts高レベルオーケストレーション