diff --git a/TODO.md b/TODO.md index 9a3ff87f..bc3158f6 100644 --- a/TODO.md +++ b/TODO.md @@ -9,7 +9,6 @@ - [ ] フルスクリーン化によるオーバーホール → [tickets/tui-fullscreen-overhaul.md](tickets/tui-fullscreen-overhaul.md) - [ ] 新しい Pod を spawn する UI の設計 → [tickets/tui-pod-spawn-ui.md](tickets/tui-pod-spawn-ui.md) - [ ] サブミット入力 - - [ ] protocol Segment 化 → [tickets/submit-segment-protocol.md](tickets/submit-segment-protocol.md) - [ ] TUI 補完 + 型付き atom 化 → [tickets/submit-tui-completion.md](tickets/submit-tui-completion.md) - [ ] メモリ機構 - [ ] ファイル形式 + Linter 土台 → [tickets/memory-file-format.md](tickets/memory-file-format.md) diff --git a/tickets/submit-segment-protocol.md b/tickets/submit-segment-protocol.md deleted file mode 100644 index 3970be14..00000000 --- a/tickets/submit-segment-protocol.md +++ /dev/null @@ -1,87 +0,0 @@ -# サブミット入力: protocol Segment 化 - -## 背景 - -現状の `Method::Run { input: String }` はユーザー入力を素の文字列で運んでいる。TUI の `Atom::Paste`(bracketed paste で受けた塊)は表示用には `[Clipboard #N | X chars, Y lines]` ラベルを持っているが、submit 時に `InputBuffer::submit_text` で flatten され、Pod や session log にはただの長い user message として伝わる。 - -memory / workflow 導入に伴い、submit には paste 以外にも以下を載せたくなる: - -- `@` のファイル参照 + auto_read -- `#` の Knowledge 参照(`docs/plan/memory.md`) -- `/` の Workflow 起動(`docs/plan/workflow.md`) - -これを protocol 上で **String + marker パース**で扱うと、Pod が正規表現と escape 規則を抱え、code block 内の `#foo` を literal にするか等の ambiguity と escape 漏洩が常に付きまとう。さらにリッチクライアント(GUI / web / ネイティブ)はファイル添付や paste を file chip / clipboard card として描くため、protocol が String だと「chip → string marker → Pod で再 parse → 表示用に再構築」という 3 段 round-trip を全 client で再実装する形になる。 - -protocol を typed segment の列に上げると、Pod は parser を持たず resolve に集中でき、client は自分の表現単位のまま intent を送れる。最低限 `Segment::Text(String)` を fallback として置くので、CLI piping 等の dumb client は 1 segment を作るだけで従来通り動く(protocol の dictation が「Text を作れること」に閉じる)。 - -## 要件 - -### protocol - -`Method::Run` の payload を `String` から `Vec` に置き換える。Segment は最低限以下の variant を持つ: - -- `Text` — 自由文字列。dumb client の fallback -- `Paste` — TUI 等の bracketed paste 由来の塊。`id` と本文を持つ -- `FileRef` — `@` の auto_read 対象 -- `KnowledgeRef` — `#` の Knowledge 参照 -- `WorkflowInvoke` — `/` の Workflow 起動 - -`Event::UserMessage` の payload も同じ `Vec` に揃え、複数 client で見たり log replay する際に typed のまま再構築できる状態にする。 - -未知 variant に対する forward compatibility(`#[serde(other)]` 相当の吸収)を入れる。 - -### Pod 側 resolve - -Pod は `Vec` を LLM context へ flatten する単一経路を持つ: - -- `Text` → そのまま -- `Paste` → 本文を inline 展開(現状 TUI が flatten してきたのと同じ結果)。**session log / Event::UserMessage 上ではラベル化情報を保持**し、表示時に `[Clipboard #N | X chars]` を再構築できること -- `FileRef` → scope 内なら本文 read + inline、scope 外は明示エラー -- `KnowledgeRef` / `WorkflowInvoke` → resolver が登録されていれば展開、未登録はフォールバック(後述) - -resolver の trait 化と memory / workflow 用 resolver 実装は別チケット。本チケットは **Text と Paste を typed のまま end-to-end で運び切る経路**で完結させる。`FileRef` 以降は variant 定義と「resolver 未登録時のフォールバック」だけ仕込んで実装は後続に委ねる。 - -### TUI 側 - -`InputBuffer::Atom::Paste` を submit 時に flatten せず、`Segment::Paste` として送出する。`Event::UserMessage` を typed segment で受けて `Block::UserMessage` を typed のまま描画(paste は引き続き magenta ラベル)。 - -text しか作れない client が引き続き存在しても良いことを protocol 仕様に明記する(`vec![Segment::Text(_)]` のみで動く)。 - -### unknown variant / 未登録 resolver の扱い - -新 variant を持つ client が古い Pod に投げる、または resolver 未登録の variant を Pod が受けた場合は **2 経路に同時に流す**: - -1. **LLM context** に `[unknown input: kind=foo]` 相当の placeholder を `Item::system_message` で差し込む。LLM が「ユーザーは何かを送ろうとしたが Pod が解釈できなかった」と気づき、ユーザーに聞き返せる状態を作る。 -2. **ユーザー向け通知チャネル**(`Event::Alert` / リネーム前は `Event::Notification`)にも同時に送る。client 側で「このサーバはこの input type を解釈できません」とユーザーに直接出せる。 - -両経路に流すことで「user も LLM も気付けない silent drop」を避ける。serde 側は unknown variant を吸収する形(`#[serde(other)]` 相当)を初期から入れる。 - -`Event::Alert` への rename は `tickets/notification-naming-cleanup.md` で扱う。本チケットの実装時点での名称(`Event::Notification` か `Event::Alert` か)はその時の repo 状態に従う。 - -## 範囲外 - -- `@` / `#` / `/` token の TUI parsing と補完 UI(`tickets/submit-tui-completion.md`) -- KnowledgeRef / WorkflowInvoke の resolver 実装(memory / workflow チケット) -- FileRef の引数拡張(行範囲、glob 等) - -## 完了条件 - -- `Method::Run` / `Event::UserMessage` が `Vec` で wire を通る -- TUI から paste した内容が `Segment::Paste` で送出され、Pod の LLM context では本文展開、Event 経由の再描画では `[Clipboard #N | ...]` が復元される -- `vec![Segment::Text(_)]` だけ送る client は従来通り動く -- 未登録 variant の扱いが決定済みルール通りに動く(決定は本チケット内で合意) -- forward compat: 未知 variant を含む payload を deserialize しても panic / parse error にならない -- 既存ビルド・テストを壊さない - -## 参照 - -- `docs/plan/memory.md` §retrieval 経路 / §Knowledge の呼び出し制御 -- `docs/plan/workflow.md` §呼び出しと依存 -- `crates/protocol/src/lib.rs`(`Method::Run`, `Event::UserMessage`) -- `crates/tui/src/input.rs`(`Atom::Paste`, `submit_text`) -- `crates/tui/src/app.rs`(`submit_input`, `Block::UserMessage` 描画) - -## Review -- 状態: Approve -- レビュー詳細: [./submit-segment-protocol.review.md](./submit-segment-protocol.review.md) -- 日付: 2026-04-27 diff --git a/tickets/submit-segment-protocol.review.md b/tickets/submit-segment-protocol.review.md deleted file mode 100644 index 086bac58..00000000 --- a/tickets/submit-segment-protocol.review.md +++ /dev/null @@ -1,60 +0,0 @@ -# Review: サブミット入力 protocol Segment 化 - -## 前提・要件の確認 - -### protocol -- `Method::Run` と `Event::UserMessage` が `Vec` で wire を通る: - - `crates/protocol/src/lib.rs:14` (`Run { input: Vec }`) / `:162` (`UserMessage { segments: Vec }`)。`tag = "kind"` の internally-tagged enum で Text/Paste/FileRef/KnowledgeRef/WorkflowInvoke/Unknown を定義 (`:97-125`)。完了。 -- 全 5 variant 定義: 完了 (`:101-119`)。 -- forward compatibility: `#[serde(other)]` で `Segment::Unknown` に吸収 (`:123`)。専用テスト 2 本 (`:416-438`) で deserialize 成功と `Method::Run` 内での共存を確認。完了。 -- dumb client 用ヘルパー: `Segment::text` (`:129`) と `Method::run_text` (`:138`) を用意し、ドキュメントコメントで「`vec![Segment::Text(_)]` のみで動く」前提を明記 (`:84-96`)。完了。 -- `Event::UserMessage roundtrip` の更新版テスト (`:749-770`) も追加済み。 - -### Pod 側 resolve -- `Pod::run` が `Vec` を受け、`flatten_segments` で単一文字列に展開 (`crates/pod/src/pod.rs:580-655`)。Text/Paste は inline、FileRef/KnowledgeRef/WorkflowInvoke/Unknown は `[unresolved : ]` プレースホルダに置換し同時に `Alert(Warn, Pod, …)` を発火。要件の **2 経路同時通知** を満たす。 -- `Pod::run_text` shim (`:561-566`) と `Method::run_text` の対応関係も整合。 -- `interrupt_and_run` も `Vec` シグネチャに揃え、内部で `self.run(input)` に委譲 (`crates/pod/src/interrupt_and_run.rs:27-47`)。 -- Controller 側で `Method::Run { input }` を受けると `Event::UserMessage { segments: input.clone() }` を broadcast し、その後 `pod.run(input)` / `interrupt_and_run(input)` に渡す (`crates/pod/src/controller.rs:273-299`)。Event 経路は typed のまま再放送される。 - -### TUI 側 -- `InputBuffer::submit_segments` が `Atom::Char` を 1 つの `Segment::Text` に collapse、`Atom::Paste` を独立した `Segment::Paste` に分離 (`crates/tui/src/input.rs:198-221`)。テスト 4 本 (`:428-490`) で純テキスト・前後 Text に挟まれた Paste・空入力・先頭 Paste のケースをカバー。 -- `submit_input` は `submit_segments()` を経由して `Method::Run { input: segments }` を送出 (`crates/tui/src/app.rs:64-81`)。空入力 (`segments_are_blank`, `:497-502`) の Pause/Resume 動作も保たれている。 -- `Block::UserMessage` が `segments: Vec` に置き換わり (`crates/tui/src/block.rs:17-19`)、`render_user_message` (`crates/tui/src/ui.rs:368-420`) が paste セグメントを magenta `[Clipboard #N | X chars, Y lines]` で再構築、Overview モードは `segment_display_text` で one-liner にする (`:425-439`)。Unknown variant は `[unknown segment]` 表示。 -- `Event::UserMessage { segments }` ハンドラが typed を直接 `Block::UserMessage` に積む (`crates/tui/src/app.rs:93-100`)。 -- `restore_history` の user message 側はテキストのみを `vec![Segment::text(text)]` でラップ (`:373-376`) — 後述の non-blocking 指摘あり。 - -### unknown variant / 未登録 resolver -- `flatten_segments` が unknown / FileRef / KnowledgeRef / WorkflowInvoke すべてに対し placeholder + `Alert` を発行 (`crates/pod/src/pod.rs:609-651`)。決定済みルール通り。 -- 統合テスト `run_with_unresolved_segment_emits_alert_and_placeholder` (`crates/pod/tests/controller_test.rs:417-467`) が `FileRef` ケースで Alert と placeholder の両発火を end-to-end で立証。 -- `run_with_paste_segment_inlines_content_and_emits_typed_user_message` (`:357-415`) が paste 経路の hybrid 性質 (LLM には inline 本文・Event::UserMessage には typed segments) を立証し、ラベルの LLM への漏洩がないことも明示的に assert (`:414`)。 - -### ライフサイクル/ビルド条件 -- 既存テスト群は `Method::run_text` / `Pod::run_text` への置換で sweep 済み (`crates/pod/examples/*.rs`、`crates/pod/tests/*.rs` 全般)。残存する直接 `Method::Run { input: ... }` は (a) submit_input の本流、(b) 新統合テスト、(c) FFI 側のアサート (`spawn_pod_test.rs:196`、`pod_comm_tools_test.rs:188`) のみで、いずれも Vec 形に揃っている。 -- `Worker::run(String)` 自体は LLM-worker 層の低レベル API なので変更しない判断は妥当。Pod が flatten 一回で接続する単一経路 (要件) と整合。 -- ビルド・テストは緑で、警告は事前から存在する `llm-worker/timeline.rs` の `end_scope` のみ — 本チケットによる退行なし。 - -## アーキテクチャ・スコープ - -- レイヤ境界: `Segment` / placeholder / Alert の生成は **Pod 層** に閉じており、`llm-worker` には漏れていない (Worker は引き続き String を受け取る)。`MEMORY.md` の「llm-worker は低レベル基盤に留める」方針を守れている。 -- TUI 側は `submit_segments` が新責務として追加されただけで、parser や resolver は持ち込まれていない。submit-tui-completion で扱う `@`/`#`/`/` 補完は範囲外、適切に分離されている。 -- 新規の resolver trait は導入されておらず、要件通り「variant 定義 + 未登録時フォールバック」で着地している。後続チケット (memory / workflow) のための余分な抽象化なし。 -- `flatten_segments` を `Pod::run` から呼ぶ形に閉じ込めた点も妥当: 本文展開ロジックを 1 箇所に集中。 -- 新依存の追加なし、新クレートの追加なし。`alerter.rs` / `notify_buffer.rs` は同チケットの notification-naming-cleanup のリネームを取り込んだ既存リファクタの一部であり、本チケットの範囲とは独立して整理されている (本チケット由来ではない)。 - -## 指摘事項 - -### Blocking -なし。 - -### Non-blocking / Follow-up -- **session log replay は paste チップを失う**: 要件本文の「session log / Event::UserMessage 上ではラベル化情報を保持」のうち、Event 経路は満たしているが session log は inlined テキストのみを保持し、`restore_history` で `vec![Segment::text(text)]` に潰れる (`crates/tui/src/app.rs:373-376`)。完了条件の方では「Event 経由の再描画では `[Clipboard #N | ...]` が復元される」と Event 経路に絞られているため本チケットでは合格判断としたが、後で GUI / 別クライアントが履歴 fetch する場面で paste 識別が失われる。完全に保持するには Worker history か session_store に typed segments を別途保存する必要があるため、後続チケット (例えば native-gui-mvp や memory 関連) で「session log にも segment metadata を残すか」を扱うのがよい。 -- **`Segment::Unknown` の end-to-end 統合テストがない**: protocol レベルの deserialize テストはあるが、Pod 統合テストでは FileRef だけが Alert + placeholder の両発火を確認している。Unknown は同一の `flatten_segments` 分岐を通るので回帰耐性は十分だが、forward-compat の信頼の証として 1 ケース足してもよい。 -- **`Event::UserMessage` 経由の paste チップ復元は live subscriber のみが受け取れる**: 後発接続クライアントは `GetHistory` で取れるのが Worker の `Item::Message`(flatten 済み文字列)だけ。上記 1 点目と同じ根の話。 - -### Nits -- `Segment::Unknown` を再シリアライズすると `{"kind":"unknown"}` になり情報損失するが、本チケットでは forward-compat の片方向だけで十分という整理 (`crates/protocol/src/lib.rs:121-122` のコメントに明記済み)。意図通り。 -- `flatten_segments` の placeholder 文言 (`[unresolved file ref: …]` 等) は LLM 側プロンプトの一部になる。将来 prompt catalog に逃がすかは別途検討すべきかも (現時点では英語固定で OK)。 -- `crates/tui/src/ui.rs:404` で `lines: line_count` と `lines` フィールドのシャドウイングを避けるため `line_count` にリネームしているのは適切。 - -## 判断 -**Approve** — チケットの要件と完了条件はすべて満たされており、forward-compat と hybrid フォールバックの両経路が end-to-end のテストで立証されている。session log 側の paste チップ保持は要件本文に言及があるものの完了条件は Event 経路に絞られているため、本チケット範囲外として後続チケットでフォローすればよい。コードベースを歪める方向の追加抽象化は持ち込まれておらず、Pod / TUI / protocol の責務分離も保たれている。