コンテンツにスキップ

Bash ツールランタイム

このドキュメントでは、エージェントのツール呼び出しで使用される bash ツール のランタイムパスについて、コマンドの正規化から実行、切り詰め/アーティファクト、レンダリングまでを説明します。

また、インタラクティブ TUI、プリントモード、RPC モード、およびユーザーが開始するバン(!)シェル実行において動作が異なる箇所も示しています。

スコープとランタイムサーフェス

Section titled “スコープとランタイムサーフェス”

coding-agent には2つの異なる bash 実行サーフェスがあります:

  1. ツール呼び出しサーフェスtoolName: "bash"):モデルが bash ツールを呼び出す際に使用されます。
    • エントリポイント:BashTool.execute()
  2. ユーザーバンコマンドサーフェス(インタラクティブ入力からの !cmd または RPC bash コマンド):セッションレベルのヘルパーパスです。
    • エントリポイント:AgentSession.executeBash()

どちらも最終的には src/exec/bash-executor.tsexecuteBash() を非 PTY 実行に使用しますが、ツール呼び出しパスのみが正規化/インターセプションとツールレンダラーのロジックを実行します。

エンドツーエンドのツール呼び出しパイプライン

Section titled “エンドツーエンドのツール呼び出しパイプライン”

1) 入力の正規化とパラメータマージ

Section titled “1) 入力の正規化とパラメータマージ”

BashTool.execute() はまず normalizeBashCommand() を通じて生のコマンドを正規化します:

  • 末尾の | head -n N| head -N| tail -n N| tail -N を構造化されたリミットとして抽出します
  • 末尾/先頭の空白をトリムします
  • 内部の空白はそのまま保持します

次に、抽出されたリミットを明示的なツール引数とマージします:

  • 明示的な head/tail 引数は抽出された値をオーバーライドします
  • 抽出された値はフォールバックとしてのみ使用されます

bash-normalize.ts のコメントには 2>&1 の除去について言及がありますが、現在の実装ではそれを削除しません。ランタイムの動作は依然として正しいですが(stdout/stderr は既にマージされています)、正規化の動作はコメントが示唆するよりも狭い範囲です。

2) オプショナルインターセプション(ブロックコマンドパス)

Section titled “2) オプショナルインターセプション(ブロックコマンドパス)”

bashInterceptor.enabled が true の場合、BashTool は設定からルールを読み込み、正規化されたコマンドに対して checkBashInterception() を実行します。

インターセプションの動作:

  • コマンドがブロックされるのは以下の 両方 が満たされた場合のみ:
    • 正規表現ルールがマッチする、かつ
    • 提案されたツールが ctx.toolNames に存在する
  • 無効な正規表現ルールは暗黙的にスキップされます
  • ブロック時、BashTool は以下のメッセージで ToolError をスローします:
    • Blocked: ...
    • 元のコマンドが含まれます

デフォルトのルールパターン(コードで定義)は一般的な誤用を対象としています:

  • ファイルリーダー(catheadtail、…)
  • 検索ツール(greprg、…)
  • ファイルファインダー(findfd、…)
  • インプレースエディタ(sed -iperl -iawk -i inplace
  • シェルリダイレクト書き込み(echo ... > file、ヒアドキュメントリダイレクション)

InterceptionResult には suggestedTool が含まれていますが、BashTool は現在メッセージテキストのみを表面化しています(details に構造化された提案ツールフィールドはありません)。

3) CWD の検証とタイムアウトのクランプ

Section titled “3) CWD の検証とタイムアウトのクランプ”

cwd はセッションの cwd(resolveToCwd)に対して相対的に解決され、その後 stat で検証されます:

  • パスが存在しない -> ToolError("Working directory does not exist: ...")
  • ディレクトリではない -> ToolError("Working directory is not a directory: ...")

タイムアウトは [1, 3600] 秒にクランプされ、ミリ秒に変換されます。

4) アーティファクトの割り当て

Section titled “4) アーティファクトの割り当て”

実行前に、ツールは切り詰められた出力の保存用にアーティファクトのパス/ID を(ベストエフォートで)割り当てます。

  • アーティファクトの割り当て失敗は致命的ではありません(アーティファクトスピルファイルなしで実行が続行されます)
  • アーティファクトの ID/パスは、切り詰め時の完全な出力の永続化のために実行パスに渡されます

BashTool は以下のすべてが true の場合にのみ PTY 実行を選択します:

  • bash.virtualTerminal === "on"
  • PI_NO_PTY !== "1"
  • ツールコンテキストに UI がある(ctx.hasUI === true かつ ctx.ui が設定されている)

それ以外の場合は非インタラクティブな executeBash() を使用します。

つまり、プリントモードと非 UI の RPC/ツールコンテキストは常に非 PTY を使用します。

非インタラクティブ実行エンジン(executeBash

Section titled “非インタラクティブ実行エンジン(executeBash)”

シェルセッション再利用モデル

Section titled “シェルセッション再利用モデル”

executeBash() はネイティブの Shell インスタンスを以下でキーイングされたプロセスグローバルマップにキャッシュします:

  • シェルパス
  • 設定されたコマンドプレフィックス
  • スナップショットパス
  • シリアライズされたシェル環境変数
  • オプショナルなエージェントセッションキー

セッションレベルの実行では、AgentSession.executeBash()sessionKey: this.sessionId を渡し、セッションごとに再利用を分離します。

ツール呼び出しパスは sessionKey を渡さ ない ため、再利用スコープはシェル設定/スナップショット/環境変数に基づきます。

シェル設定とスナップショットの動作

Section titled “シェル設定とスナップショットの動作”

各呼び出し時に、エグゼキュータは設定のシェル構成(shellenv、オプショナルな prefix)を読み込みます。

選択されたシェルに bash が含まれる場合、getOrCreateSnapshot() を試行します:

  • スナップショットはユーザー rc からのエイリアス/関数/オプションをキャプチャします
  • スナップショットの作成はベストエフォートです
  • 失敗した場合はスナップショットなしにフォールバックします

prefix が設定されている場合、コマンドは以下のようになります:

<prefix> <command>

Shell.run() はチャンクをコールバックにストリーミングします。エグゼキュータは各チャンクを OutputSink とオプショナルな onChunk コールバックにパイプします。

キャンセル:

  • 中止シグナルは shellSession.abort(...) をトリガーします
  • ネイティブ結果からのタイムアウトは cancelled: true + アノテーションテキストにマッピングされます
  • 明示的なキャンセルも同様に cancelled: true + アノテーションを返します

タイムアウト/キャンセル時にエグゼキュータ内で例外はスローされません。構造化された BashResult を返し、呼び出し元にエラーセマンティクスのマッピングを委ねます。

インタラクティブ PTY パス(runInteractiveBashPty

Section titled “インタラクティブ PTY パス(runInteractiveBashPty)”

PTY が有効な場合、ツールは runInteractiveBashPty() を実行し、オーバーレイコンソールコンポーネントを開いてネイティブの PtySession を駆動します。

動作のハイライト:

  • xterm-headless 仮想ターミナルがオーバーレイにビューポートをレンダリングします
  • キーボード入力は正規化されます(Kitty シーケンスやアプリケーションカーソルモードの処理を含む)
  • 実行中の esc は PTY セッションを終了します
  • ターミナルのリサイズは PTY に伝播されます(session.resize(cols, rows)

無人実行のための環境ハードニングデフォルトが注入されます:

  • ページャー無効化(PAGER=catGIT_PAGER=cat など)
  • エディタプロンプト無効化(GIT_EDITOR=trueEDITOR=true、…)
  • ターミナル/認証プロンプトの削減(GIT_TERMINAL_PROMPT=0SSH_ASKPASS=/usr/bin/falseCI=1
  • 非インタラクティブ動作のためのパッケージマネージャー/ツール自動化フラグ

PTY 出力は正規化され(CRLF/CRLF に、sanitizeText)、アーティファクトスピルサポートを含めて OutputSink に書き込まれます。

PTY の起動/ランタイムエラー時、シンクは PTY error: ... 行を受け取り、コマンドは未定義の終了コードで完了します。

出力処理:ストリーミング、切り詰め、アーティファクトスピル

Section titled “出力処理:ストリーミング、切り詰め、アーティファクトスピル”

PTY パスと非 PTY パスの両方が OutputSink を使用します。

  • インメモリの UTF-8 セーフなテールバッファを保持します(DEFAULT_MAX_BYTES、現在 50KB)
  • 総バイト数/行数を追跡します
  • アーティファクトパスが存在し、出力がオーバーフローした場合(またはファイルが既にアクティブな場合)、完全なストリームをアーティファクトファイルに書き込みます
  • メモリしきい値がオーバーフローした場合、インメモリバッファをテール(UTF-8 境界セーフ)にトリムします
  • オーバーフロー/ファイルスピルが発生した場合に truncated をマークします

dump() は以下を返します:

  • output(アノテーション付きプレフィックスの可能性あり)
  • truncated
  • totalLines/totalBytes
  • outputLines/outputBytes
  • アーティファクトファイルがアクティブな場合は artifactId

ランタイムの切り詰めは OutputSink のバイトしきい値ベースです(デフォルト 50KB)。このコードパスでは、ハードな 2000 行の制限は強制されません。

非 PTY 実行の場合、BashTool は部分更新用に別の TailBuffer を使用し、コマンド実行中に onUpdate スナップショットを発行します。

PTY 実行の場合、ライブレンダリングは onUpdate テキストチャンクではなく、カスタム UI オーバーレイによって処理されます。

結果の整形、メタデータ、エラーマッピング

Section titled “結果の整形、メタデータ、エラーマッピング”

実行後:

  1. cancelled の処理:
    • 中止シグナルが中止されている場合 -> ToolAbortError をスロー(中止セマンティクス)
    • それ以外 -> ToolError をスロー(ツール失敗として扱われます)
  2. PTY の timedOut -> ToolError をスロー
  3. 最終出力テキストに head/tail フィルターを適用(applyHeadTail、head が先で tail が後)
  4. 空の出力は (no output) になります
  5. toolResult(...).truncationFromSummary(result, { direction: "tail" }) を通じて切り詰めメタデータを付加
  6. 終了コードのマッピング:
    • 終了コードが欠落 -> ToolError("... missing exit status")
    • 非ゼロ終了 -> ToolError("... Command exited with code N")
    • ゼロ終了 -> 成功結果

成功ペイロードの構造:

  • content:テキスト出力
  • 切り詰め時の details.meta.truncation。以下を含みます:
    • directiontruncatedBy、総/出力の行数+バイト数
    • shownRange
    • 利用可能な場合は artifactId

ビルトインツールは wrapToolWithMetaNotice() でラップされているため、切り詰め通知テキストは最終テキストコンテンツに自動的に追加されます(例:Full: artifact://<id>)。

ツール呼び出しレンダラー(bashToolRenderer

Section titled “ツール呼び出しレンダラー(bashToolRenderer)”

bashToolRenderer はツール呼び出しメッセージ(toolCall / toolResult)に使用されます:

  • 折りたたみモードでは視覚的な行切り詰めプレビューを表示します
  • 展開モードでは現在利用可能なすべての出力テキストを表示します
  • 警告行には切り詰め理由と、切り詰め時の artifact://<id> が含まれます
  • タイムアウト値(引数から)はフッターのメタデータ行に表示されます

注意事項:完全なアーティファクト展開

Section titled “注意事項:完全なアーティファクト展開”

BashRenderContext には isFullOutput がありますが、現在のレンダラーコンテキストビルダーは bash ツール結果に対してそれを設定しません。展開ビューは、別の呼び出し元が完全なアーティファクトコンテンツを提供しない限り、結果コンテンツ内のテキスト(テール/切り詰められた出力)を依然として使用します。

ユーザーバンコマンドコンポーネント(BashExecutionComponent

Section titled “ユーザーバンコマンドコンポーネント(BashExecutionComponent)”

BashExecutionComponent はインタラクティブモードでのユーザーの ! コマンド用です(モデルのツール呼び出しではありません):

  • チャンクをライブストリーミングします
  • 折りたたみプレビューは最後の 20 論理行を保持します
  • 1行あたり 4000 文字で行クランプします
  • メタデータが存在する場合、切り詰め + アーティファクト警告を表示します
  • キャンセル/エラー/終了状態を個別にマークします

このコンポーネントは CommandController.handleBashCommand() によって配線され、AgentSession.executeBash() からデータが供給されます。

サーフェスエントリパスPTY 対象ライブ出力 UXエラーの表面化
インタラクティブツール呼び出しBashTool.executeはい、bash.virtualTerminal=on かつ UI が存在し PI_NO_PTY!=1 の場合PTY オーバーレイ(インタラクティブ)またはストリーミングテール更新ツールエラーは toolResult.isError になります
プリントモードツール呼び出しBashTool.executeいいえ(UI コンテキストなし)TUI オーバーレイなし;出力はイベントストリーム/最終アシスタントテキストフローに表示同じツールエラーマッピング
RPC ツール呼び出し(エージェントツーリング)BashTool.execute通常 UI なし -> 非 PTY構造化されたツールイベント/結果同じツールエラーマッピング
インタラクティブバンコマンド(!AgentSession.executeBash + BashExecutionComponentいいえ(エグゼキュータを直接使用)専用の bash 実行コンポーネントコントローラーが例外をキャッチし UI エラーを表示
RPC bash コマンドrpc-mode -> session.executeBashいいえBashResult を直接返しますコンシューマーが返されたフィールドを処理
  • インターセプターは、提案されたツールが現在のコンテキストで利用可能な場合にのみコマンドをブロックします。
  • アーティファクトの割り当てが失敗した場合、切り詰めは依然として発生しますが、artifact:// の逆参照は利用できません。
  • シェルセッションキャッシュはこのモジュールに明示的なエビクションがありません。ライフタイムはプロセススコープです。
  • PTY と非 PTY のタイムアウトサーフェスは異なります:
    • PTY は明示的な timedOut 結果フィールドを公開します
    • 非 PTY はタイムアウトを cancelled + annotation サマリーにマッピングします