maestro/docs/tools/ssh-console-tools.md
oss-sync b1292e34b2
Some checks failed
CI / build-and-test (push) Has been cancelled
sync: update from private repo (edc775f2)
2026-07-06 01:04:12 +00:00

15 KiB

SSH Console Tools (SshConsoleEnsure / SshConsoleSend / SshConsoleSnapshot)

AI と人間が共有する SSH PTY セッションを操作する 3 ツール。1 タスクは接続ごとに 1 つ、複数の PTY セッションを同時に持てる(例: 2 台のサーバーへ同時接続して並行作業)。各セッション内では cd / 環境変数 / foreground プロセスが job をまたいで維持される。長時間の対話作業 / TUI (vim, top, less, tmux) / 複数ラウンドの調査向け。

単発コマンドだけなら SshExec (ssh-ops piece) のほうが軽い。本ツール群は対話的シェル + AI が画面を見続ける用途に最適化されている。

ユーザーが先にセッションを開いている場合がある: タスク詳細の Console タブから、ユーザーがタブを追加して自分でセッションを起動できる(複数タブを同時に開ける。→SSH 接続)。その場合 SshConsoleEnsure は既存セッションをそのまま再利用する。「まず console を開く」操作を AI 側でやり直す必要はない。ただしタスクに複数セッションが同時に開いている場合、connection_id を省略して自動採用されるのは「直前にこのツール群で操作した接続 (current-connection pointer)」か「セッションが 1 つしかない」ときだけ。2 つ以上開いていて pointer も無い状態で省略すると ambiguous エラーになる(詳細は後述)。

典型的な flow (まずこれを真似る)

// 1. どの接続が使えるか発見 (タスク本文に UUID が無いとき)
SshListConnections({})
// → {"connections":[{"id":"abcd1234-...","label":"prod-aao","host":"...","host_key_verified":true}]}

// 2. セッション確保 (冪等。何度呼んでも同じセッションを返す)
SshConsoleEnsure({ connection_id: "abcd1234-..." })
// → {"ok":true,"reused":false,"connection_id":"abcd1234-...","cols":120,"rows":32}

// 3. コマンドを送信。改行で実行される
SshConsoleSend({
  connection_id: "abcd1234-...",
  input: "uptime\n",
  wait_ms: 800,        // 出力が落ち着くまで待つ ms (default 500, max 5000)
})
// → {"ok":true,"bytes_sent":7,"screen_after":"... load average: 0.05 ...","new_output_bytes":120}

// 4. screen_after で見切れた場合は scrollback を取得
SshConsoleSnapshot({
  connection_id: "abcd1234-...",
  kind: "scrollback",
  max_bytes: 32768,
})
// → {"kind":"scrollback","byte_count":12345,"truncated":false,"text":"..."}

SshConsoleEnsure

セッションを確保する (無ければ open、有れば再利用)。冪等SshConsoleSend を呼ぶ前に必須ではない (auto-ensure される) が、最初に明示的に呼んでおくと「セッション開設に成功した」ことを確認できる。

Param Required Description
connection_id yes UUID。このワークスペースに登録された接続である必要がある。label / hostname / 思い出した文字列で代用してはいけない — 必ず SshListConnectionsid を渡すこと
cols no 初回 open 時のターミナル幅。default ssh.console.default_cols (120)
rows no 初回 open 時のターミナル高さ。default ssh.console.default_rows (32)
force_replace no bool。default false同じ connection_id に既存セッションがあるとき、それを閉じて開き直すかどうかを制御 (下記参照)。別の connection_id を渡した場合は force_replace に関係なく新規セッションが追加される

Return:

{"ok": true, "reused": <bool>, "connection_id": "...", "cols": 120, "rows": 32, "host_fingerprint": "SHA256:..."}

reused: true なら過去ターンから引き継いだ既存セッション (cd 等の state あり)。false なら今回新規 open。

複数セッション: 別の接続を開くと「追加」される (重要)

1 つのタスクは接続ごとに 1 つ、複数のセッションを同時に持てる。SshConsoleEnsure別の connection_id を渡しても既存セッションは閉じられない — 単に新しい接続へのセッションが追加で開くだけ (add-not-replace)。同じ connection_id を渡した場合だけ「再利用か force_replace での再起動か」の分岐になる:

  • 同じ connection_id + force_replace: false (default) → 既存セッションを再利用 (alreadyActive: true, reused: true)
  • 同じ connection_id + force_replace: trueその接続のセッションだけ connection_change 理由で閉じて開き直す (他の接続のセッションには影響しない。旧 shell の state は失われる)

タスクあたりの上限 (既定 5、上限到達時は task_session_cap) やユーザー単位の上限 (user_session_cap、設定次第) に達すると新規オープンは拒否される。人間側も Console タブの「+ 接続」ボタンで同じように接続を追加でき、タブの ✕ で不要なセッションを閉じられる (→SSH 接続)。

connection_id を省略できるのは 1 セッションのときだけ

SshConsoleSend / SshConsoleRun / SshConsoleSnapshotconnection_id を省略できるが、解決順は次のとおり:

  1. connection_id を明示 → その接続のセッションを厳密に使う (無ければ not found エラー。勝手に新規 open はしない)
  2. 省略時は、このツール群で直前に操作した接続 (current-connection pointer) が生きていればそれ
  3. pointer も無く、タスクのセッションがちょうど 1 つならそれを自動採用
  4. タスクに複数セッションが同時に開いていて pointer も無い場合 → ambiguous エラー (connection_id required (multiple sessions open: ...))。この場合は connection_id を明示しないと動かない
  5. セッションが 1 つも無い場合 → SshConsoleEnsure で開くよう促すエラー

複数接続で並行作業するときの鉄則: 2 つ目以降のセッションを触るときは常に connection_id を明示する。省略に頼ってよいのは、セッションが 1 つしかないタスクか、直前にそのセッションを操作した直後だけ。

典型的なバグパターン: ジョブをまたいで動作するエージェントが connection_id を覚えておらず、複数セッションが開いている状態で省略 → ambiguous で reject される、というケース。エラーメッセージに出ている connection_id の一覧 (または SshListConnections) から正しいものを選んで明示する。

SshConsoleSend

入力を送る。printable な shell コマンド (改行なし、制御文字なし、2 文字以上) には server が自動で末尾に \n を付加して実行する。例: input: "ls -la" でも input: "ls -la\n" でも同じ結果。

auto-append が発火した時は response に auto_newline_appended: true が載るので、必要なら呼び出し側で検知できる。

raw のまま送りたい (改行を付けない) ケース:

  • sudo の password prompt に応答中 (echo OFF — タイプ + 別 Send で \n)
  • vim の insert mode で文字を順に打鍵
  • less / top / htop 等 TUI で 1 キー操作 (q, j, k, space, etc.)
  • これらは制御文字を含むか 1 文字なので auto-append は発火しない。
Param Required Description
connection_id no UUID。タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可。複数セッションが開いていて pointer も無いと ambiguous エラー (下記「connection_id を省略できるのは 1 セッションのときだけ」参照)
input yes raw 文字列。LF / CRLF / control 文字 (\x03 Ctrl-C, \x04 Ctrl-D, \x1b Esc, \t Tab) を透過
wait_ms no 送信後の screen_after 取得までの待ち時間 (default 500ms, max 5000ms)

Return:

{
  "ok": true,
  "bytes_sent": 7,
  "screen_after": "user@your-host:~$ uptime\n 12:34 ...",
  "new_output_bytes": 120
}

入力フィルタ

各 line は connection 側の deny_patterns / allow_patterns (および組み込み deny-list) と照合される。1 行でも NG にひっかかると入力全体が reject される (部分実行はしない)。エラー例: SshConsoleSend: line 2 rejected by builtin_deny (rm\s+-rf).

TUI 操作のコツ

  • vim 起動: SshConsoleSend({input: "vim test.txt\n", wait_ms: 1000}) → 待ってから SshConsoleSnapshot で画面確認
  • vim 抜ける: SshConsoleSend({input: "\x1b:q!\n"}) (\x1b は Esc)
  • top/htop 抜ける: SshConsoleSend({input: "q"})
  • 走行中プロセス中断: SshConsoleSend({input: "\x03"}) (Ctrl-C)
  • パス完成 (Tab): SshConsoleSend({input: "ls /var/lo\t"}) (Tab だけ送って screen で候補確認)

よくある間違い

  • wait_ms が短すぎて screen_after に出力が間に合わない → 再度 SshConsoleSnapshot で取り直す
  • printable input は server が自動で \n を付加するので改行忘れは基本問題ない。raw 入力したい場合 (TUI 操作等) は制御文字を含めること
  • 大量出力で screen_after が切れる → SshConsoleSnapshot({kind: "scrollback"}) で取得

SshConsoleSnapshot

Param Required Description
connection_id no UUID。タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可。複数セッションが開いていて pointer も無いと ambiguous エラー (上記「connection_id を省略できるのは 1 セッションのときだけ」参照)
kind no screen (デフォルト) — 現在の表示画面 / scrollback — それ以前を含む過去の出力
max_bytes no scrollback の上限 (default 8192, max 65536)。tail から max_bytes バイト返す

Return (kind=screen):

{"kind":"screen","cols":120,"rows":32,"text":"...","cursor":{"x":0,"y":15}}

Return (kind=scrollback):

{"kind":"scrollback","byte_count":123456,"truncated":true,"text":"..."}

text は ANSI escape strip 済み (色 / cursor 移動シーケンスを除去)。raw が必要な場合は audit log を参照。

SshConsoleRun

通常のコマンドは SshConsoleRun を使う。 raw SshConsoleSend は対話操作 (vim/REPL/sudo/TUI) と本当の中断専用。長いコマンドを Ctrl-C で殺さないこと。Ctrl-C は confirm_interrupt:true が必要。

シェルコマンドを実行し、完了まで blocking して {done, exit_code, output} を返す。SshConsoleSend のように「画面取得のタイミングを気にしてから結果を読む」作業が不要で、終了コードも自動取得できる。

Param Required Description
command yes 実行するシェルコマンド
connection_id no UUID。タスクにセッションが 1 つだけ、または直前にこのツール群で操作した接続 (pointer) がある場合は省略可。複数セッションが開いていて pointer も無いと ambiguous エラー
timeout_ms no タイムアウト (ms)。デフォルト 120000 (2分)、最大 600000 (10分)。タイムアウト時もコマンドは kill されない
idle_ms no 出力が idle_ms ms 途切れたら早期終了と判定する。0=無効 (デフォルト)

Return:

{
  "done": true,
  "exit_code": 0,
  "output": "... コマンドの出力 ..."
}

done: false はタイムアウトで終了したケース。exit_code は shell が返した終了コード (非 0 はエラー)。

SshConsoleRun vs SshConsoleSend の使い分け

用途 使うツール
通常のシェルコマンド (ls, grep, systemctl, make, ...) SshConsoleRun
対話的 TUI (vim, top, htop, tmux, ...) SshConsoleSend + SshConsoleSnapshot
REPL / sudo パスワード入力 SshConsoleSend
プロセス中断 (Ctrl-C) SshConsoleSend({input: "\x03", confirm_interrupt: true})
長時間バックグラウンドを待つ SshConsoleRun({timeout_ms: 300000})

よくある間違い

  • 長時間コマンドに timeout_ms を指定し忘れる → デフォルト 2 分でタイムアウト。timeout_ms を伸ばすこと
  • コマンドが止まらないからと安易に Ctrl-C を送る → confirm_interrupt:true を付けた SshConsoleSend が必要。SshConsoleRun の途中で別の SshConsoleSend を送ってはいけない
  • done: false を無視してそのまま次に進む → コマンドはまだ動いている可能性がある。SshConsoleSnapshot で状態確認

エラー時のリカバリ

エラー 対応
host_key_* UI (Settings → User Folder → SSH Connections) で TOFU 検証してから再試行
command_rejected (builtin_deny / custom_deny) deny-list で reject。admin に許可パターン追加を相談 (ローカルで回避してはいけない)
idle_timeout / duration_cap 古いセッションが閉じた。SshConsoleEnsure を再度呼んで開け直す
connection_change 同じ connection_idforce_replace: true 付き Ensure が呼ばれた → その接続のセッションだけ閉じて開き直した (別の接続を開いても発生しない)
connection_id required (multiple sessions open: ...) (ambiguous) Send/Run/Snapshot で connection_id を省略したが、このタスクに複数セッションが同時に開いていて pointer も無い。エラー文中の一覧から狙った connection_id を明示する
this task already has the maximum of N open console sessions (task_session_cap) タスクあたりのセッション上限 (既定 5) に到達。使っていない接続を SshConsoleSend/Run の完了を確認してからユーザーに Console タブでタブを閉じてもらうか、既存接続を使い回す
you already have the maximum of N open console sessions (user_session_cap) ユーザー単位の上限 (設定されている場合) に到達。他タスクのセッションを閉じるまで新規オープンは拒否される
maintenance admin の対応を待つ。complete({status: 'needs_user_input', missing_info: 'SSH maintenance window'}) で停止
not initialised ssh.enabled または ssh.console.enabled が false / MCP_ENCRYPTION_KEY 未設定。admin に依頼
not registered to this workspace 対象接続がこのワークスペースに未登録。Settings → SSH で登録し、Settings → Tools で ssh カテゴリを有効化する

deny-list の限界

deny-list は first line of defense であって信頼境界ではない。bash -c "..."$VAR 経由の動的展開は通る。多層防御 (audit + abuse lock + admin kill) で運用する。

機密値 (token / password / SSH key) は input 文字列に直接書かない。サーバー側の env / config / secrets manager から読ませる。