feat: サブエージェントの実行状況を追えるようにする - #138
Merged
Merged
Conversation
セッション詳細のログ機械(可視域の実測・スクロール・範囲選択・URL クリック・端の 自動スクロール)を ui/log-pane.tsx へ移す。**振る舞いは変えない**(既存テスト 3021 件は 1 つも書き換えずに緑)。サブエージェント専用のログ画面と同じ実装を共用するための前段で、 「触っていない行がコピーされる」「行が虫食いで落ちる」類の不具合が片方の画面だけで 再発しないようにする。 - 状態行(常時 1 行)は LogPane の中に含める。あれは「ログの高さを状態で変えない」ための 予約行なので、ログ本体と離すと片方の画面で条件付きに戻されうる - handleMouse は boolean を返し、押下の裁定順(コンポーザが先)は view に残す (useComposer.handleMouse と同じ契約) - useInput は持たない(1 画面 1 useInput は view のまま) - 既定の行 prefix はモジュール定数にする。logLines のメモ化の依存に入るので、 描画ごとに新しい関数を渡すと全エントリが毎フレーム再展開される
SDK は `system/task_progress`(説明の動的更新・last_tool_name・usage)や `task_notification` の status / summary / output_file を流しているのに、codiva は task_id の集合(完了ゲート)以外を全部捨てていた。サブエージェント内部のツール実行も `parent_tool_use_id` 付きで親ストリームに来るので、親の作業と無標識で混ざっていた。 `SessionState.subagents`(transient)に実行状況と**専用ログ**を持たせる。 **完了ゲート(activeTaskIds)には手を入れない。** ゲートは「素の id 集合 + tasks_changed の REPLACE による自己修復」という一番壊れにくい形で、そこへ上限付き・ 決着済みも保持する表示用の構造体を挟むと (a) 追い出しが生きたタスクをゲートから消す =早すぎる完了、(b) status の分類ミスでゲートが永久に埋まる= running の張り付き、を 作り込む。寿命も違う(ゲートはターン終端で必ず捨てる / 記録はターンをまたいで残すのが 機能要件)。意図的な二重管理で、番人は agent-events.spec.ts の「表示用メタあり/なしで activeTaskIds・deferredResult・status の遷移が完全一致する」表。 - ログ行の振り分けは fold 側(parse は parent_tool_use_id を subagentRef として載せる だけ)。**帰属先が引けなければ親ログへ落とす** = 未知の provider・未知の id でも 行を捨てない(従来どおりの見た目に degrade するだけ) - task_progress はゲートを触らない(進捗はエッジでもレベル信号でもないので、これを 起点に積むと「進捗だけ来て決着が来ない」新しい wedge の経路になる) - tasks_changed も表示を触らない(あれは live *background* タスクの集合で、実採取の 前景 Task では一度も出ない。表示を REPLACE すると走っているものが終わって見える) - 決着の成否は task_notification.status / task_updated.patch.status から。**未知の値でも 必ず決着イベントを出す**(ゲートを解くのが最優先。分類は stopped へ丸める) - ログ予算は LogLimits として引数化し、サブエージェント 1 本は 200 件 / 12k 文字。 全 8 本ぶんでも親(400k)の半分に収まることを spec で数値固定 - ターン終端と CLI 再起動では記録を残して「走っている印」だけ封じる(そのプロセスの タスクは二度と決着を報告しないので、封じないとスピナーが永久に回る) - summarizeToolUse に Agent / Task を追加 → 裸の `Agent` が `Agent "Create report.txt file" (general-purpose)` になる(transcript.ts と共用なので 復元ログの体裁も同時に改善) - AgentCapabilities.subagents を追加(claude のみ true)。ただし**表示の可否は capability で決めない** — /agent で切り替えたセッションでも、そのセッションが実際に 走らせたサブエージェントの履歴は残るべきなので state.subagents の空判定で決める 実データ(__fixtures__/session-subagent.jsonl)の番人を claude-parse.spec.ts に追加。
セッション詳細のログ下段にサブエージェントの実行状況が 1 行で出て、クリック(または `/subagents`)でそのサブエージェント専用のログ画面へ入れるようにする。内部のツール実行は 親のログに混ざらず、そちらへ振り分けられる(core 側は前のコミット)。 **行は増やしていない。** 最初は独立した 1 行にして DETAIL_CHROME_ROWS を 9 にしたが、 そこから引き算する dialogMaxRows のせいで 24 行の端末では質問ダイアログの選択肢が窓に 1 件も入らなくなった(tests/app.test.tsx の「低い端末でも Ask がログを潰さず…」が検出)。 結局あれは操作ヒント行と排他だったので同じ 1 行に畳んである。優先順位は 認証 → 再開 → サブエージェント → Ctrl+C → 空行。Ctrl+C より優先してよいのは、あれが フォーカス横断の chord で案内が無くても効くのに対し「何が走っているか」はここでしか 分からないから。逆に認証・再開は行動を促す案内なので譲る(そのときサブエージェントは 走っていない)。 - クリックは **press では開かず release で開く**(drag が来たら取り消す)・**左ボタン限定**・ **ラベルの表示幅の内側だけ**(行末より右の余白は何も無いので当たりにしない)・ 許可/質問待ちの間は遷移しない(アンマウントするとダイアログの内部 state が捨てられる) - 複数件はピッカー(`ui/subagent-picker.tsx`)。**自分の useInput を持たない** presentational で、キーは詳細ビューの単一ハンドラが処理する。表示ウィンドウは親が 1 度だけ組んで描画と当たり判定の両方へ渡す(別々に計算すると押した行とズレる) - 専用画面(`ui/subagent-detail.tsx`)は **入力欄を持たない**(サブエージェントに指示は 送れないので、あるように見せない)。出口は Esc 一本で、Ctrl+C は親セッションのターンを 中断する。ログの機械は Phase 1 の `useLogPane` を共用するので範囲選択・URL クリック・ 端の自動スクロールが詳細ビューと完全に同じ - 経過時間は provider が報告した所要時間を優先し、走っている間だけ `useClock(ms, enabled)` で毎秒描き直す(詳細ビューに無条件の 1 秒タイマーを持ち込まない) - `/subagents` を追加(マウス無効環境でのキーボード経路)。別名は付けない — `agents` は `/agent` の前方一致に混ざり、`tasks` は TODO リストと紛らわしい `tests/helpers.ts` に `drivenManager` / `asMsg` を集約(app.test.tsx のローカル定義を移動)。
- ARCHITECTURE.md: 完了ゲート(activeTaskIds)と表示(subagents)を**意図的に二重管理** している理由 3 点(寿命が違う / 有界な表示から live set を導出すると早すぎる完了か 永久 running を作る / レベル信号は前景 Task では一度も出ないので表示に使えない)と、 ログ行の振り分けを畳み込み側に置いた理由 - rules/sdk-integration.md: 崩してはいけない 4 点(subagents から live set を導出しない / tasks_changed は表示を触らない / task_progress はゲートを触らない / 決着の分類を 外してもゲートは必ず解ける)と、capability で表示可否を決めない契約 - rules/ink-components.md: 常時 1 行の枠を増やす前に既存の 1 行と排他かを疑う (DETAIL_CHROME_ROWS を 1 増やして低い端末のダイアログを壊した実例)/ ログの機械は useLogPane に統一 / 読み取り専用画面には入力欄を置かない / 画面遷移は release で 左ボタン・ラベル幅の内側だけ - TECH_NOTES.md: task_started / task_progress / task_notification が実際に運ぶフィールドの 表と、前景 Task では background_tasks_changed が出ないという実測 - CLAUDE.md: コードの地図に subagents.ts / subagent-row.ts / log-pane.tsx などを追加 - README.ja.md + README.md(対で): 「サブエージェントの実行状況を追う(/subagents)」節 - TASKS.md: follow-up(transcript 復元・stream_event の帰属・一覧への表示・detailStateRef)
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 を通してある。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
何を
エージェントがサブエージェント(Claude Code の Task ツール)を起こしたとき、いま codiva からは何も見えません。実行状況を追えて、そのサブエージェント専用のログまで潜れるようにします。
Escで戻る)。マウス無効環境では/subagents。/subagents)でピッカーから選べます。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/sealSubagentscore/subagent-row.ts(新規)core/agent-events.tsのrouteLogEntrycore/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 つあります。clearTurnStateで必ず捨てるもの(残すと次のターンの完了を飲み込んで永久running)。記録はターンをまたいで残すのが機能要件です。statusの分類ミスで永久runningのどちらかを新しい扉から作り込みます。ゲートは「素の文字列集合 + レベル信号の REPLACE による自己修復」という一番壊れにくい形のままにしました。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 画面 1useInputは 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 倍になるため)。parent_tool_use_idを載せるだけで状態を持たず、帰属先が引けなければ親ログへ落とすので未知の provider・未知の id でも行を捨てません。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 / 内部ツール実行が専用ログへ分離され親には混ざらない /status5 値の表(どれでもゲートは解ける)。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)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 つのサブエージェントを並列で調査に使って」のような指示が手軽です)。Escで詳細へ戻る(一覧まで飛ばない)Enterで開けるCtrl+O/ クリックの畳み込み・範囲選択・URL クリックが効く(feat: 会話ログのツール実行を畳み、ユーザーの発言に地の色を付ける #137 との組み合わせ)