作者が5月ごろから個人で開発しているソース公開サービス「Artifact Share」(AIエージェントが生成したHTMLやMarkdownをURLで共有するサービス)のCLIに、preview というローカルコマンドを追加した話。ブラウザ上で生成ファイルの要素をクリックして指摘を書くと、Claude Code・Codex・Cursor がファイルを修正し、ブラウザが自動リロードで結果を表示するというレビーループを実現している。本記事はその設計判断・実装のまとめ。
preview <file> を実行すると、そのプロセス自体がローカルサーバーになる。
# 人間側: プレビューを開く(ブラウザが開き、以後は画面上で指摘する)
npx @artifactshare/cli preview ./report.html
# エージェント側: 1周はこの2コマンド
npx @artifactshare/cli preview next --wait 90 # 指摘が届くまで待って受け取る
npx @artifactshare/cli preview done --stdin # 直した結果を報告する
next) → 編集・保存 → 結果を報告する (done)」.md / .html。配信は 127.0.0.1 のローカルマシンのみ一番苦労したのはここ。こだわりは「待っている間も会話セッションを邪魔しない」こと。フォアグラウンドで待機コマンドを実行すれば確実だが、待機中はターンが塞がり他の作業を頼めなくなる。その仕組みがエージェントごとに違った。
| エージェント | 使えた仕組み |
|---|---|
| Claude Code | バックグラウンドタスク完了時の自動ターン再開 |
| Codex | 実行中セッションへのメッセージのキュー投入 |
| Cursor | ACPで常駐させた専用セッションへの固定プロンプト送信 |
| (共通の保険) | 「届くまで待つ」コマンドをフォアグラウンドで実行(ターンは塞がる) |
Bashコマンドをバックグラウンド実行できる標準機能を利用。preview next --wait 3600 をバックグラウンドに仕込んでおくと、バックグラウンドタスク完了時に新しいターンが自動で開く。指摘が届いた瞬間にコマンドが終了し、Claude Code が起きてファイルを直しに行く。修正後は次の待機を仕込み直してループが続く。
Codexにはバックグラウンド完了でターンを再開する仕組みがない(openai/codex#32188)ため別の手段を使う。previewサーバーが起動時にCodexのセッションIDを検出しておき、指摘が届いたらそのセッションへ「届いた」というメッセージをキュー投入。エージェントが指摘を取りに来るまでバッチは消費されず、セッション終了後も codex resume で再開すれば受け取れる。
CursorにはIDEチャットを外部から起こす手段自体がない。使ったのは ACP(Agent Client Protocol)。previewに同梱した専用ランチャーが、ACP経由の専用セッションを1ワークスペースに1つ常駐させ、指摘が届くと「バッチが届いた」という固定プロンプトだけを送信。本文はエージェントが自分で取りに行き、使用中なら通知は保留されバッチは保存されたまま残る。
preview <file> コマンド自身がサーバーになり、Ctrl-Cで終了。同じファイルへの2回目の preview は既存セッションへ再接続するだけtype PreviewAnchor =
| { kind: 'artifact' }
| { kind: 'text'; state: 'attached' | 'orphaned'; quotedText: string; prefixText: string; suffixText: string; cssPath: string | null }
| { kind: 'element'; state: 'attached' | 'orphaned'; selector: string; label: string; contextText: string }
element をCSSセレクタ単独にしないのがこだわり。編集でセレクタの指す先がずれても、ラベル(例: button "送信")と周辺テキストを突き合わせて位置を再解決できるorphaned に落とし、誤った場所には紐づけない指摘の状態は draft → requested → in_progress → resolved / dismissed の5つに整理。遷移の主体を固定している:
draft 側の遷移(作成・削除・バッチ送信)は人間だけrequested 以降の前進(着手・解決・見送り)はエージェントだけ(reopenのみ人間)generation 番号が増え、報告はこの番号と紐づく。古い番号の報告は無視されるため、重複報告しても二重適用されない