Skip to content

feat: 会話ログのツール実行を畳み、ユーザーの発言に地の色を付ける - #137

Merged
takecchi merged 1 commit into
mainfrom
codiva/codiva-claude-code-claude-code-bash-cat
Sep 8, 2026
Merged

feat: 会話ログのツール実行を畳み、ユーザーの発言に地の色を付ける#137
takecchi merged 1 commit into
mainfrom
codiva/codiva-claude-code-claude-code-bash-cat

Conversation

@takecchi

@takecchi takecchi commented Sep 8, 2026

Copy link
Copy Markdown
Owner

何を

セッション詳細のログを Claude Code の transcript に寄せて、会話の筋がそのまま読める表示にします。

1. 連続したツール実行を 1 行に畳む(Ctrl+O / クリック / /tools

作業ログは ⏺ Bash(…) / ⎿ … の 2 行組が延々と続くため、読みたい本文(説明と自分の指示)がツールの実況に埋もれていました。

> release/stg も同様ですかね
⏺ ▸ 3 ファイルを読み込み・5 個のコマンドを実行
はい、stg も同じ設定で動きます。
  • クリック 1 回でそのまとまりを開閉( になり中身が下に出る)。押した時点では開かず、離した時点で開く(ドラッグでの範囲選択と衝突させないため。URL クリックと同じ規則)。
  • Ctrl+O で一括開閉(Claude Code と同じキー)。パレットからは /toolsCtrl+C と同じくフォーカス横断の chord なので、許可待ちでログを読み返している最中にも効きます。
  • いま動いている作業は畳まない — ログの末尾のまとまりはそのまま全部見えていて、次の本文・指示が届いた時点で畳まれます。
  • ツール呼び出しが 1 件だけのまとまりも畳まないRead src/core/scroll.ts はそれ自体で読めるので、畳むとファイル名が消えて情報が減るだけだからです。
  • 開閉するときはスクロールのアンカーを固定して、押した見出しが画面上で動かないようにしています。

2. ユーザーの発言に地の色

kind: 'user' の行(> の行)に背景色を付けます。長いやり取りを遡るとき、文字を読まずに「どこで自分が喋ったか」を探せるようにするためです。背景は文字のあるところだけ(行末までは伸ばさない)— 行を空白で埋めると、その空白が範囲選択のコピーに混ざるため。

どう作ったか

役割 場所
どこが 1 まとまりか / まとめ行の文言(純粋) core/log-collapse.ts(新規)の collapsibleRunAt / toolRunLabel
行への落とし込み core/scroll.tslogLines(…, collapse)。取っ手は DisplayLine.group
クリックの当たり判定 core/log-selection.tslogGroupAt
展開状態・キー操作 ui/session-detail.tsx
ツールの種類 LogEntry.toolAgentToolKind
  • AgentToolKindcore/agent-events.tscore/types.ts へ移し(LogEntry が持つため)、read / search を追加。Claude / Codex / Grok の各アダプタと、復元経路(core/transcript.ts)も同じ写像を通します。
  • ついでに Read / Grep / Glob の要約にパス・パターンを出すようにしました(まとめを開いたときに中身が分かるように。従来はツール名だけ)。
  • メモ化は壊していません。エントリの行は従来どおり ENTRY_ROWS にキャッシュされ、毎回作り直すのは 1 まとまり 1 行の見出しだけ。見出しの文字列は件数が変わるまで不変なので、Ink の測定キャッシュにも溜まりません(docs/ARCHITECTURE.md「React の dev ビルドとヒープ枯渇」の制約)。

設定

~/.codiva/config.jsoncollapseToolLogs(既定 true)。/config からも切り替えられます。false でも Ctrl+O / /tools でその場で畳めます。

テスト

  • src/core/log-collapse.spec.ts(新規)… まとまりの成立条件(先頭が tool_use / 末尾は畳まない / 1 件は畳まない / エージェント切替で切る)と文言の組み立て(順序・単複・空)をテーブルドリブンで。
  • src/core/scroll.spec.ts … collapse 無しで従来どおり / 畳むと見出し 1 行 + group が付く / 展開すると見出しの下に中身 / 見出しの折り返し / 切替の区切り行との併用。
  • src/core/log-selection.spec.tslogGroupAt(行のどこでも当たる)。
  • src/ui/log-line.spec.tsx(新規)… 地の色を持つ kind は user だけ、という意図の番人。
  • tests/app.test.tsx … 実 SDK メッセージを流して「畳まれている → Ctrl+ORead a.ts / Bash ls が出る」まで通す統合テスト。

ローカルで lint / typecheck / test(3083 件)/ build の 4 点を通しています。

手動確認のお願い

非対話セッションなので実際の TUI 操作(クリックでの開閉・地の色の見え方)は確認できていません。node <codiva>/dist/index.js で以下をご確認いただけると助かります。

  • まとめ行のクリックで開閉できる(ドラッグで範囲選択したときは開閉しない)
  • Ctrl+O / /tools で一括開閉できる
  • 開閉しても押した見出しが画面上で動かない
  • / が端末で 1 セル幅で描かれ、行がズレない
  • ユーザー発言の地の色が使っている端末テーマで読める(暗い/明るいどちらでも)

セッション詳細のログは `⏺ Bash(…)` / `⎿ …` の 2 行組が延々と続くため、読みたい
本文(アシスタントの説明とユーザーの指示)がツールの実況に埋もれていた。
Claude Code の transcript と同じように、まとまりを 1 行へ畳んで会話の筋が
そのまま読める表示にする。

- `core/log-collapse.ts`(新規・純粋): 連続したツール実行のうち「畳めるまとまり」を
  探す `collapsibleRunAt` と、まとめ行の文言を組む `toolRunLabel`。
  - **末尾のまとまり(いま動いている作業)は畳まない** — 次の本文・指示が届いた
    時点で畳む。
  - **ツール呼び出しが 1 件だけのまとまりも畳まない** — ファイル名が消えて情報が
    減るだけで、行数も 2 → 1 にしかならないため。
- `core/scroll.ts`: `logLines(…, collapse)` がまとめ見出しの行を出す。取っ手は
  `DisplayLine.group`(`links` と同じ「行に貼り付いた当たり判定の情報」)。
  エントリ単位のメモ化は従来どおりで、見出しの文字列は件数が変わるまで不変。
- `core/log-selection.ts`: 見出しのクリック判定 `logGroupAt`。
- `ui/session-detail.tsx`: クリックで 1 つ開閉(押した時点では開かない = URL と同じ)、
  `Ctrl+O` / `/tools` で一括開閉。開閉時はアンカーを固定して押した見出しが動かない
  ようにする。
- `types.ts`: `AgentToolKind` を `core/types.ts` へ移し `read` / `search` を追加、
  `LogEntry.tool` に載せる(内訳の出所)。各アダプタと復元(`transcript.ts`)も追従。
- `claude-parse.ts`: `Read` / `Grep` / `Glob` の要約にパス・パターンを出す
  (まとめを開いたときに中身が分かるように)。
- `ui/theme.ts` / `ui/log-line.tsx`: ユーザーの発言(`kind: 'user'`)に地の色。
  長いやり取りで「どこで自分が喋ったか」を読まずに探せるようにする。
- 設定 `collapseToolLogs`(既定 true / `/config` から切替可)と README ja/en・
  docs/ARCHITECTURE.md を更新。
@takecchi
takecchi merged commit 1bfec43 into main Sep 8, 2026
1 check passed
@takecchi
takecchi deleted the codiva/codiva-claude-code-claude-code-bash-cat branch September 8, 2026 01:19
takecchi added a commit that referenced this pull request Sep 8, 2026
main の `/tools`(会話ログのツール実行を畳む・#137)と、このブランチのサブエージェント
表示が同じ場所(session-detail.tsx のログ機械・コマンドレジストリ・ログ行の型)を触って
いたので、**両方を残す**形で解決した。

- `src/ui/session-detail.tsx`: main 版をベースに、このブランチの変更(ログ機械の
  `useLogPane` への委譲・サブエージェント行・ピッカー・`/subagents`)を載せ直した。
  11 箇所の衝突マーカーを手で繕うより、main の畳み込み配線を壊さない確実な経路を選んだ
- `src/ui/log-pane.tsx`: **ツール実行の畳み込みを共通実装へ取り込んだ**(`collapse` /
  `onToggleGroup` / `beforeToggle`)。畳み込みの機械(press で保留 → drag で取消 →
  release で開閉、アンカーの固定)は pane が持ち、state(`grouping` / `openRuns`)は
  view が持つ。おかげでサブエージェント専用ログでも `Ctrl+O` / クリックの開閉が同じに動く
- `src/core/agent-events.ts`: `routeLogEntry`(サブエージェントへの振り分け)に main の
  `LogEntry.tool`(畳み込みの内訳)を載せる。`AgentToolKind` は main で `types.ts` へ
  移ったので、そこから引く
- `src/core/commands.ts`: `/tools` と `/subagents` の両方を登録(どちらも詳細ビュー専用)
- `src/app.tsx`: `collapseTools` と `onOpenSubagent` の両方を `SessionDetail` へ渡す
- README(ja/en)・CLAUDE.md: 両方の節を残し、コードの地図では畳み込みの当たり判定が
  `ui/log-pane.tsx` に移ったことを反映

lint / typecheck / test(124 files・3213 tests)/ build を通してある。
takecchi added a commit that referenced this pull request Sep 8, 2026
## 何を

エージェントが**サブエージェント**(Claude Code の Task ツール)を起こしたとき、いま codiva
からは何も見えません。実行状況を追えて、**そのサブエージェント専用のログまで潜れる**ようにします。

```
⏺ サブエージェント: general-purpose · 実行中 · Writing report.txt · Write · 12s
```

- 詳細ビューのログ下段に **1
行**で出ます(種別・状態・今やっていること・直近に使ったツール・経過時間)。「今やっていること」はサブエージェント側の進捗に合わせて書き換わります。
- **その行をクリックすると専用のログ画面**へ入ります(`Esc` で戻る)。マウス無効環境では **`/subagents`**。
- 複数走っているときは「他 N 件」に畳み、クリック(または `/subagents`)でピッカーから選べます。
- **内部のツール実行が親のログに混ざらなくなります**。専用ログ側にまとまり、親には「何を任せたか」の 1 行だけが残ります。
- 終わったあとも記録は残るので結果を読み返せます(1 セッション 8 本まで・transient)。

SDK は以前からこれらを流していて、codiva が `task_id`
の集合(完了ゲート)以外を全部捨てていました。**ついでに直るもの**:

- 裸の `Agent` だったログ行が `Agent "Create report.txt file" (general-purpose)`
になります(`summarizeToolUse` は `transcript.ts` と共用なので復元ログの体裁も同時に改善)。
- `task_notification.status` を読むようにしたので、サブエージェントの**成否が区別**されます(従来はどちらも同じ
settle)。

## どう作ったか

| 役割 | 場所 |
|---|---|
| 記録のリスト操作(純粋) | `core/subagents.ts`(新規)の `startSubagent` /
`progressSubagent` / `settleSubagent` / `sealSubagents` |
| 下段 1 行の文言・当たり判定・ピッカーの選択肢(純粋) | `core/subagent-row.ts`(新規) |
| 内部ログ行の振り分け | `core/agent-events.ts` の `routeLogEntry` |
| SDK メッセージの解釈 | `core/claude-parse.ts`(`task_progress`
ハンドラ新設・`task_notification` の成否・`parent_tool_use_id`) |
| ログの機械(**両画面で共通**) | `ui/log-pane.tsx`(新規)の `useLogPane` + `<LogPane>`
|
| 専用ログ画面 / ピッカー | `ui/subagent-detail.tsx` /
`ui/subagent-picker.tsx`(新規) |

### 完了ゲートには触っていません(意図的な二重管理)

実行状況と専用ログは `SessionState.subagents`
という**別フィールド**で持ちます。ゲート(`activeTaskIds`)と統合しなかった理由が 3 つあります。

1. **寿命が違う**。ゲートは `clearTurnState` で必ず捨てるもの(残すと次のターンの完了を飲み込んで永久
`running`)。記録は**ターンをまたいで残す**のが機能要件です。
2. **記録は有界で決着済みも保持する**(8 本)。ここから live set
を導出すると、追い出しが生きたタスクをゲートから消して**早すぎる完了**か、`status` の分類ミスで**永久 `running`**
のどちらかを新しい扉から作り込みます。ゲートは「素の文字列集合 + レベル信号の REPLACE
による自己修復」という一番壊れにくい形のままにしました。
3. **レベル信号は表示に使えない**。`background_tasks_changed` が運ぶのは *live
**background** tasks* で、実採取した前景の Task では**一度も出ていません**。これで表示を REPLACE
すると走っているものが「終わった」と表示される嘘になります。

番人として **「表示用メタあり /
なしでゲートの遷移(`activeTaskIds`・`deferredResult`・`status`)が完全一致する」**テーブル駆動
spec を置いています。同じ理由で `task_progress` はゲートを触らず、決着の分類を外しても `task_settled` は
`status` が何であれ必ず出します。

### 行は増やしていません(一度失敗しました)

最初はサブエージェント行を独立した 1 行にして `DETAIL_CHROME_ROWS` を 9 にしました。ところがそこから引き算する
`dialogMaxRows` のせいで、**24 行の端末で質問ダイアログの選択肢が窓に 1
件も入らなくなりました**(既存テスト「低い端末でも Ask がログを潰さず…」が検出)。結局あれは操作ヒント行と排他だったので、同じ 1
行に畳んであります。

優先順位は **認証 → 再開 → サブエージェント → `Ctrl+C` → 空行**。`Ctrl+C`
より優先してよいのは、あれがフォーカス横断の chord
で案内が無くても効くのに対し「何が走っているか」はこの行でしか分からないからです。逆に認証・再開は行動を促す案内なので譲ります(そのときサブエージェントは走っていません)。

### ログの機械を 1 実装に統一しました

会話ログを描く画面が 2 つになるので、可視域の実測・スクロール・範囲選択・URL クリック・端の自動スクロール・幅変更での選択破棄を
`useLogPane` へ切り出しました(**振る舞い不変の単独コミット**。既存 3021 件のテストを 1
つも書き換えずに緑)。ここは「簡略コピー」が黙って壊れる場所(`flexShrink={0}` 忘れ → 行の虫食い、幅変更のリセット忘れ →
触っていない行がコピーされる)なので、片方の画面だけで再発しないようにしています。

`useInput` はフックに持たせず(1 画面 1 `useInput` は view のまま)、`handleMouse` が
boolean を返して**押下の裁定順を view に残す**契約です(`useComposer.handleMouse` と同じ)。

**#137 のマージで、ツール実行の畳み込みもここへ取り込みました。** 機械(press で保留 → drag で取消 → release
で開閉、アンカー固定)は pane が持ち、state(`grouping` / `openRuns`)は view
のまま。おかげで**専用ログ画面でも `Ctrl+O` / クリックの開閉が同じに動きます**。

### その他

- ログの予算は `LogLimits` として引数化し、サブエージェント 1 本は 200 件 / 12k 文字。**全 8
本ぶんでも親(400k)の半分に収まる**ことを spec で数値固定しています(素朴に配列を N 本増やすと予算が N 倍になるため)。
- 内部ログ行の振り分けは畳み込み側。parse は `parent_tool_use_id`
を載せるだけで状態を持たず、**帰属先が引けなければ親ログへ落とす**ので未知の provider・未知の id でも行を捨てません。
- 経過時間は provider が報告した `usage.duration_ms` を優先し、走っている間だけ `useClock(ms,
enabled)` で毎秒描き直します(詳細ビューに無条件の 1 秒タイマーを持ち込まない)。
- `AgentCapabilities.subagents` は足しましたが、**表示の可否はこれで決めません** — `/agent`
で切り替えたセッションでも、そのセッションが実際に走らせたサブエージェントの履歴は残るべきなので `state.subagents`
の空判定で決めます。

## テスト

- `src/core/subagents.spec.ts`(新規)… start
の冪等・進捗が記録を作らない・**最初の決着が勝つ**・上限は「決着済み最古」から落ちる・`appendSubagentLog` の未知 ref
は undefined(呼び側が親ログへ落とす契約)。
- `src/core/agent-events.spec.ts` … **ゲート不変条件の表**(メタあり/なしで完全一致)+
ログ振り分けの表(`subagentRef` が undefined / 既知 / 未知 × 記録あり/なし →
**どのセルでも行が消えない**)+ `seq` の重複なし + 終端遷移で記録が残り印だけ封じられる。
- `src/core/claude-parse.spec.ts` …
**実データ**(`__fixtures__/session-subagent.jsonl`)で `task_progress`
が効いている(`description` が "Writing report.txt" に・`lastTool: 'Write'`)/
決着の成否・要約・出力先・usage / **内部ツール実行が専用ログへ分離され親には混ざらない** / `status` 5
値の表(**どれでもゲートは解ける**)。
- `src/core/subagent-row.spec.ts`(新規)… 代表が揺れない・ラベルを必ず表示幅に切る(返す `width`
が実測値と一致)・当たり判定は**行末より右を拾わない**・ピッカーの逆算が `choiceRowHeights` と整合。
- `src/core/log-buffer.spec.ts` / `src/ui/log-pane.spec.tsx`(新規)…
予算の差し替え / 描画行数が高さを超えない・**状態行は idle でも scrollback でも 1 行**(高さ不変の番人)。
- `tests/subagent.test.tsx`(新規)… 実 SDK メッセージを流して **0 件 / 1 件 / 3
件でフレームの行数が変わらない**・クリックで専用画面へ(press だけ・drag
あり・右ボタン・行末より右では**遷移しない**)・ピッカーの ↑↓ / Enter / Esc・`/subagents` の 3 分岐。

`tests/helpers.ts` に `drivenManager` / `asMsg` を集約しました(`app.test.tsx`
のローカル定義を移動)。

ローカルで `lint` / `typecheck` / `test`(**124 files・3213 件**、#137 のぶんも含む)/
`build` の 4 点を通しています。

## スコープ外(follow-up は `docs/TASKS.md`)

- **transcript からの復元**。`core/transcript.ts` は `isSidechain` 行を捨てており、CLI
の jsonl に `parent_tool_use_id` / task id
が載るかを**実測できていません**(フィクスチャにサブエージェントのターンが含まれていない)。想定で書けない箇所なので、`sdk-spike`
で確かめてから別途。載っていなければ `task_notification.output_file`
を読むほうが筋が良いです。復元後のセッションでは 0 件になります。
- `stream_text` の帰属(サブエージェントのストリーミングが `parent_tool_use_id` を運ぶか未実測)/
一覧へのサブエージェント数の表示 / 詳細ビューの表示状態をビュー切替で保つ(`detailStateRef`)。

## 手動確認のお願い

非対話セッションなので実際の TUI 操作は確認できていません。`node <codiva>/dist/index.js`
で以下をご確認いただけると助かります(サブエージェントを起こすには「3 つのサブエージェントを並列で調査に使って」のような指示が手軽です)。

- [ ] 下段の 1 行に種別・状態・進捗が出て、進捗に合わせて書き換わる
- [ ] その行のクリックで専用ログ画面へ入れて、`Esc` で詳細へ戻る(一覧まで飛ばない)
- [ ] 複数件で「他 N 件」に畳まれ、クリック → ピッカー → `Enter` で開ける
- [ ] 専用ログ画面でも `Ctrl+O` / クリックの畳み込み・範囲選択・URL クリックが効く(#137 との組み合わせ)
- [ ] 24 行程度の低い端末で、質問ダイアログの選択肢が窓に入る(行を増やさなかったことの確認)
- [ ] 終わったサブエージェントの記録を後から開いて結果が読める
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant