yoi/tickets/tui-system-message-render.md

4.9 KiB
Raw Blame History

TUI で role:system の system message を表示する

背景

Pod は user 入力の @<path> chip / /<slug> chip を submit 時に展開し、Item::system_messagepending_attachments 経由で worker.history に commit している:

  • @<path> ライブ submit: Pod::runresolve_file_refs (crates/pod/src/pod.rs:825,852) → PodFsView::resolve_file_ref (crates/pod/src/fs_view.rs:119) で [File: <path>]\n<body> を生成
  • compact worker の auto-read: mark_read_required で nominate された再読対象が PodFsView::render_auto_read (crates/pod/src/fs_view.rs:84) で [Auto-read file: <path>:<range>]\n<body> として乗る
  • /<slug> ライブ submit: Pod::runresolve_workflow_invocations (crates/pod/src/pod.rs:826,876) → crate::workflow::resolve_workflow_invocation (crates/pod/src/workflow/mod.rs:74) で [Workflow /<slug>]\n<body> と requires Knowledge 毎の [Workflow /<slug> requires Knowledge #<req>]\n<body> を生成

いずれも PodInterceptor::on_prompt_submit (crates/pod/src/ipc/interceptor.rs:114) で PromptAction::ContinueWith(extras) 経由で worker.history に commit され、history.json に永続化されている (CLAUDE.md「許される加工」原則に整合)。

問題

LLM のコンテキストには乗っているが TUI には何も出ない。TUI 側で role:system の Item が表示経路に乗っていないため:

  1. ライブ側: 解決済み system message を運ぶ broadcast event が protocol::Event に存在しない (crates/protocol/src/lib.rs:204326)。UserMessage@<path> / /<slug> chip 自体は表示されるが、解決された本体は出ない。失敗時のみ Alert で出るが、Alert はユーザー向け一過性通知で永続化されない (crates/protocol/src/lib.rs:328348) ため表示経路として不適切。
  2. 履歴復元側: App::restore_history (crates/tui/src/app.rs:650702) の match が role: "user" / "assistant" 以外を _ => {} で握り潰す。history.json に system role で残っているのに resume 時に消える。

結果として「@<path>/<slug> を submit したのに、本当に読まれたのか / 何が context に乗ったのか TUI からは判別できない」状態になっている。

要件

  • Item::system_message (role:system) を user / assistant メッセージと並列のログ要素として TUI に表示する一般的な仕組みを入れる。種別ごとの個別パッチではなく、role:system が来たら一律で表示経路に乗せる形にする。
  • 仕組みとして最小限カバーすべき system message:
    • [File: <path>] (ライブ @<path>)
    • [Auto-read file: <path>:<range>] (compact 後の auto-read)
    • [Workflow /<slug>]
    • [Workflow /<slug> requires Knowledge #<req>]
  • 表示の単一の情報源は永続化された history (= history.json の role:system Item)。ライブ submit 時 / 履歴復元時 / 別 client subscribe 時 の三経路で同じ Block バリアントを通る。
  • 表示は本文プレビュー(数行 + 残行数 / 切り詰め注記)程度で良い。[Auto-read file: ...:<range>] の range ラベルは可視化する。workflow 本体と requires Knowledge は同じ workflow 起動に紐づく一連と分かる粒度で識別できる。
  • 解決失敗時の従来経路は維持: @<path>Alert、workflow は user-invocation エラーとして即座に Pod から返る (Pod::validate_workflow_invocations)。

範囲外 / 非目標

  • <system-reminder> 注入機構そのものの汎用化や、notify_wrapper 適用後の本文表示。これらは別所(session-todo-reminder 等)。本チケットは表示側の仕組みのみで、将来 <system-reminder> 系が role:system Item として history に乗るようになれば、同じ表示経路にそのまま流れる前提。
  • 表示形式の凝った装飾(シンタックスハイライト / 折り畳み UI。最初は素のテキストプレビューで十分。
  • model_invokation: true のみの workflowuser_invocable=falseの表示は対象外。

完了条件

  • @<path> を submit すると、user message ブロックに続けて自動読み取り結果が TUI に出る。本文プレビューが視認できる。
  • /<slug> を submit すると、workflow 起動結果のログ要素が TUI に出る。requires がある場合は Knowledge 注入もそれと分かる形で出る。
  • compact 後の resume で [Auto-read file: ...] が同じログ要素として復元・表示される。
  • 別 client が後から subscribe して Event::History を受けた場合も、同じログ要素として描画される。
  • ライブ event と history 復元の表示が一致する(同じ Block バリアントを通る)。
  • 解決失敗時の従来経路(Alert / user-invocation エラー)は維持される。