5.8 KiB
5.8 KiB
Review: protocol: ToolResult に summary を分離する
前提・要件の確認
Event::ToolResultにsummary: Stringを追加: 満たしている(crates/protocol/src/lib.rs:112-125)。outputとis_errorは残す(用途: summary は 1 行、output は詳細本文): 満たしていない。実装はoutput: Stringをcontent: Option<String>にリネーム + オプショナル化している(crates/protocol/src/lib.rs:117-124)。詳細本文フィールド自体は残っているが、名前と型が要件と食い違う。後述 Blocking 参照。ToolOutput.summaryがそのまま protocol に乗るように worker / pod 層を通す: 満たしている。worker にon_tool_resultコールバックを追加(crates/llm-worker/src/worker.rs:302-323, 777-780)、pod コントローラーでEvent::ToolResultを発火(crates/pod/src/controller.rs:189-197)。従来この variant を produce するコードは存在しなかったため、この経路新設は必須。Event::Historyreplay でも summary が欠落しないこと: 満たしている。Item::ToolResultの serde 形は既に{type: "tool_result", summary, content}で(crates/llm-worker/src/llm_client/types.rs:71-82)、TUI のrestore_historyがそれに合わせてitem["summary"]を読むように修正された(crates/tui/src/app.rs:342-348)。- 既存 TUI は summary を無視するだけで従前通り動く: 満たしている。live handler / restore_history ともにコンパイル通過、意味的にも summary を表示するようになっている(
crates/tui/src/app.rs:140-155, 342-348)。
アーキテクチャ・スコープ
- worker 既存の callback 流儀(
on_tool_use_block/on_turn_end/on_usage/on_warning)と整合した形でon_tool_resultを追加しており、拡張方法として proportionate。 - callback が
post_tool_callinterceptor とtool_output_limitstruncation の後に fire されるという不変条件が明示され(worker.rs:310-316, 742-780)、履歴に入る payload と一致する。この設計選択は妥当で、documentation も揃っている。 Mutable/Lockedいずれの遷移経路でもtool_result_cbsが運搬されており、state 型安全性を崩していない(worker.rs:1300, 1375)。- TUI 側修正は protocol 変更に追従する最小限で、
tui-fullscreen-overhaul.mdの範疇(ブロックモデル、折りたたみ、レンダラ dispatch 等)に踏み込んでいない。スコープは守られている。 restore_historyのitem["output"]→item["summary"]は一見 drive-by だが、Item::ToolResultの serde 形は元から{summary, content}であり、変更前は History 経路で 常に空文字 を表示していた既存バグ。「Event::Historyreplay でも summary が欠落しない」「既存 TUI は summary を無視するだけで従前通り動く」という要件を満たすために必要な修正で、スコープ内と判断できる。
指摘事項
Blocking
output→content: Option<String>の改名と Option 化は要件違反(crates/protocol/src/lib.rs:117-124,crates/pod/src/controller.rs:191-196,crates/tui/src/app.rs:140-154)。- 要件は「
outputとis_errorは残す」。実装はoutput: Stringをcontent: Option<String>に差し替えている。 - かつチケットの「範囲外」セクションが明示的に "
ToolOutput.contentの protocol 化。現時点では不要。必要になったら別チケット" と記している。ToolOutput側のフィールド名 (content) と意味(prune で消せる詳細本文) を protocol に持ち込んでいるため、これは「範囲外」に真正面から抵触する。 - ツール内部モデルと名前を揃えたい動機は理解できるが、意思決定は別チケットで扱うべきで、この PR で先取りすべきではない。
- 対処方針: 以下のいずれか。
- チケットの指示通り
output: Stringに戻す(意味的に truncation 後の詳細本文を入れる)。pod 側はresult.content.unwrap_or_default()を詰めるか、summaryをフォールバックにするかを worker 層の semantics に合わせて選ぶ。 - 仕様変更として妥当だと判断するなら、チケット本文の要件行と「範囲外」の該当項目を 先に 書き換えてから実装を通す(CLAUDE.md "b. 詳細化や前提の変化" の手順)。
- チケットの指示通り
- 要件は「
Non-blocking / Follow-up
Event::ToolResultの wire 互換性に破壊変更が入る点はチケット範囲内なのでそれ自体は問題ないが、Blocking を (1) で解消する場合と (2) で解消する場合で最終形が変わる。方針確定後にテストの assertion (lib.rs:520-542) とomits_absent_contentテストの取り扱いを合わせて決めること。tool_result_cbsの error-path カバレッジはない。ToolResult::error(...)がsummary: error_message, content: None, is_error: trueで emit されることを示すテストを callback_test に追記するのは follow-up として価値がある(既存テストは success ケースのみ)。
Nits
- なし。コード本体の命名・module 配置・docstring はプロジェクト慣習と整合。
判断
Request changes — 要件の文面と「範囲外」に明示的に反する protocol フィールド名変更(output → content: Option<String>)が含まれており、これは判断を伴う仕様変更なのでチケット更新を先行させるか、チケット通りに戻す必要がある。それ以外の worker / pod / TUI / テストの取り扱いはすべて proportionate で問題なし。