feat(desktop): AgentManagerライブストリームを折りたたみグループ化#199
Conversation
ToolCallごとに1行出力されて情報過多だったAgentManager のライブストリームを、VSCode版ClaudeCode拡張のように ツール実行を折りたたみグループ化するよう変更。 - groupStreamEvents: 連続する tool_use / tool_result / raw イベントを1つのグループに畳む - assistant_text / result / error / system_init は ナラティブの区切り点なので個別行のまま残す - StreamToolGroup: 折りたたみ行コンポーネント。件数 バッジ (🔧N件 / ✓N件) と最後のツール呼び出しの サマリだけ表示し、クリックで全詳細を展開 プリセット/フォルダ/kindなど 「全面刷新」 系の要望は 別PRで段階的に取り込む方針。 Refs #192
📝 WalkthroughWalkthrough新しいtodo-agent設定永続化システムを追加。JSONファイルベースのキャッシュ機構、TRPC設定エンドポイント、プリセット分類とワークスペーススコープ対応、データベース移行、UIコンポーネント統合を実装。 Changes
Sequence Diagram(s)sequenceDiagram
participant UI as UI Component
participant TRPC as TRPC Router
participant Settings as Settings Module
participant FileSystem as File System
participant Cache as Memory Cache
rect rgba(200, 150, 255, 0.5)
Note over UI,Cache: Settings Retrieval Flow
UI->>TRPC: todoAgent.settings.get()
TRPC->>Settings: getTodoSettings()
Settings->>Cache: Check cache
alt Cache Hit
Cache-->>Settings: Return cached value
else Cache Miss
Settings->>FileSystem: Read settings.json
alt File Exists
FileSystem-->>Settings: JSON content
Settings->>Settings: Parse & validate
else File Missing
Settings->>Settings: Use defaults
end
Settings->>Cache: Store in memory
end
Settings-->>TRPC: TodoSettings object
TRPC-->>UI: Settings data
end
rect rgba(150, 200, 255, 0.5)
Note over UI,Cache: Settings Update Flow
UI->>TRPC: todoAgent.settings.update(patch)
TRPC->>Settings: updateTodoSettings(patch)
Settings->>Settings: Merge with current
Settings->>Settings: Validate merged result
Settings->>Cache: Update cache
Settings->>FileSystem: Write formatted JSON
FileSystem-->>Settings: Write complete
Settings-->>TRPC: Updated TodoSettings
TRPC-->>UI: Confirmation & data
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2023e30f73
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (toolBucket.length === 0) return; | ||
| items.push({ | ||
| type: "tools", | ||
| id: `tools:${toolBucket[0]?.id ?? ""}:${toolBucket.length}`, |
There was a problem hiding this comment.
Use stable keys for grouped tool rows
The grouped row key includes toolBucket.length, so while a tool block is still streaming, every newly appended event changes the key and forces StreamToolGroup to remount. That resets its local open state to false, so an expanded group collapses again on the next chunk and users cannot inspect live tool output without repeatedly reopening it.
Useful? React with 👍 / 👎.
| ev.kind === "tool_use" || | ||
| ev.kind === "tool_result" || | ||
| ev.kind === "raw" |
There was a problem hiding this comment.
Stop folding raw prompt events into tool groups
Treating raw as a tool event regresses stream readability because this codebase emits user prompts as kind: "raw" in appendUserEvent (see apps/desktop/src/main/todo-agent/supervisor.ts). Those prompt checkpoints now get hidden inside collapsed tool groups, often showing 🔧 0件 and the fallback summary ツール実行, which mislabels non-tool content and makes iteration prompts easy to miss.
Useful? React with 👍 / 👎.
- グループキーから toolBucket.length を外し、先頭イベントidのみを キーにした。毎チャンクで StreamToolGroup が remount されて 展開状態が消える問題を解消 - raw を tool グループに畳まないように変更。raw は appendUserEvent でユーザプロンプトとしても使われており、 iteration boundary がツール塊の中に埋もれて読みづらくなる ので個別行として残す Refs: Codex review on PR #199
大幅なUI刷新: - ストリーム表示をVSCode ClaudeCode拡張のIN/OUTグリッド レイアウトに合わせてリライト。tool_use+tool_resultを ペアリングしたToolCallCardで表示(折りたたみではなく常時表示) - assistant_text / result / error / system_init は種別ごとに 専用のMessageRowで描画 - PresetsDialogにプリセット/設定タブを追加。設定タブで デフォルト最大イテレーション・タイムアウト(分)・ 最大同時実行数を変更可能 - 設定はJSONファイルとしてuserDataディレクトリに永続化 - TodoModalが設定のデフォルト値を自動反映 - TodoButtonのバッジを全ワークスペース横断(listAll)に変更、 実行中タスクにpulsingドット付きバッジ、待機タスクに+N表示 Refs #192
VSCode Claude Code拡張のwebview/index.cssを直接解析し、本物と 同じ折りたたみUI+IN/OUTグリッドレイアウトに書き直した。 - <details>/<summary>ベースに変更。ツールコールはデフォルトで 折りたたまれ、summaryはbold太字ツール名+monospaceでlink-color の二次情報(ファイルパス等)の2行構成 - 本体(.rr)を開くとgrid-template-columns: max-content 1fr の grid-subgridレイアウトでIN/OUT表示(拡張の .ir/.lo/.tr と同等) - StreamViewの親をabsolute inset-0にして、カード圧縮問題を 修正(従来はflex-shrinkで履歴が潰れていた) - assistant_text/result/errorは枠線なしのborder-leftスタイルに 簡素化。system_initは1行アイコンバッジに - raw eventも<details>で折りたたみ プリセット拡張準備: - todoPromptPresetsテーブルに kind (system/description/goal) と workspaceId (null=global) を追加。migration 0055 生成 - zodスキーマとtRPC create/updateも対応
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
apps/desktop/src/main/todo-agent/settings.ts (1)
8-12: 軽微な非効率性:mkdirSyncが毎回呼び出される
getSettingsPath()が呼び出されるたびにmkdirSyncが実行されます。getTodoSettings()はキャッシュ後は1回のみですが、updateTodoSettings()は毎回呼び出します。recursive: trueにより冪等性は保証されていますが、ディレクトリ存在チェックを追加することで若干効率化できます。ただし、設定更新は頻繁な操作ではないため、現状でも問題ありません。
♻️ オプショナル: ディレクトリ作成を初回のみに
+let dirEnsured = false; + function getSettingsPath(): string { const dir = path.join(app.getPath("userData"), "todo-agent"); - mkdirSync(dir, { recursive: true }); + if (!dirEnsured) { + mkdirSync(dir, { recursive: true }); + dirEnsured = true; + } return path.join(dir, SETTINGS_FILE); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/desktop/src/main/todo-agent/settings.ts` around lines 8 - 12, getSettingsPath currently calls mkdirSync(dir, { recursive: true }) every time, which is redundant; modify it so the directory is only created when missing (e.g., check fs.existsSync(dir) or maintain an initialized flag) to avoid repeated mkdirSync calls while keeping behavior intact; update references in getTodoSettings and updateTodoSettings to rely on the revised getSettingsPath so directory creation happens only on first need.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/desktop/src/renderer/features/todo-agent/TodoButton/TodoButton.tsx`:
- Around line 30-44: TodoButton is using electronTrpc.todoAgent.listAll.useQuery
which returns sessions across all workspaces, causing incorrect counts; change
the data source to only the current workspace by either calling
electronTrpc.todoAgent.list.useQuery with the current workspaceId or, if keeping
listAll, filter allSessions by workspaceId before computing
runningCount/queuedCount; update references to allSessions, runningCount,
queuedCount, and activeCount accordingly so only sessions where
session.workspaceId === workspaceId are included.
In `@apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx`:
- Around line 1030-1055: pairStreamEvents currently pairs only a single adjacent
tool_use with a tool_result; change it to collapse consecutive tool invocations
into one grouped StreamItem (e.g., type "toolGroup") so runs of
tool_use/tool_result become one item. Implement by iterating with an index, and
when you see a tool_use start a group collecting toolUse entries and
corresponding toolResult (if next event is tool_result) in arrays, advancing the
index past consumed events until the next non-tool event; for non-tool events
continue producing message items as before. Update references to StreamItem
consumers (e.g., MessageRow or any renderers expecting single tool entries) to
handle the new group fields (toolUses/toolResults) accordingly.
---
Nitpick comments:
In `@apps/desktop/src/main/todo-agent/settings.ts`:
- Around line 8-12: getSettingsPath currently calls mkdirSync(dir, { recursive:
true }) every time, which is redundant; modify it so the directory is only
created when missing (e.g., check fs.existsSync(dir) or maintain an initialized
flag) to avoid repeated mkdirSync calls while keeping behavior intact; update
references in getTodoSettings and updateTodoSettings to rely on the revised
getSettingsPath so directory creation happens only on first need.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8bba990d-7c13-4f7f-bf3c-f400c1ccffed
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (11)
apps/desktop/src/main/todo-agent/settings.tsapps/desktop/src/main/todo-agent/trpc-router.tsapps/desktop/src/main/todo-agent/types.tsapps/desktop/src/renderer/features/todo-agent/TodoButton/TodoButton.tsxapps/desktop/src/renderer/features/todo-agent/TodoManager/PresetsDialog/PresetsDialog.tsxapps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsxapps/desktop/src/renderer/features/todo-agent/TodoModal/TodoModal.tsxpackages/local-db/drizzle/0055_todo_preset_kind_workspace.sqlpackages/local-db/drizzle/meta/0055_snapshot.jsonpackages/local-db/drizzle/meta/_journal.jsonpackages/local-db/src/schema/todo-prompt-presets.ts
| const { data: allSessions } = electronTrpc.todoAgent.listAll.useQuery( | ||
| undefined, | ||
| { refetchInterval: 3000 }, | ||
| ); | ||
|
|
||
| const activeCount = (sessions ?? []).filter( | ||
| (session) => | ||
| session.status === "queued" || | ||
| session.status === "preparing" || | ||
| session.status === "running" || | ||
| session.status === "verifying", | ||
| const runningCount = (allSessions ?? []).filter( | ||
| (s) => | ||
| s.status === "preparing" || | ||
| s.status === "running" || | ||
| s.status === "verifying", | ||
| ).length; | ||
| const queuedCount = (allSessions ?? []).filter( | ||
| (s) => s.status === "queued", | ||
| ).length; | ||
| const activeCount = runningCount + queuedCount; |
There was a problem hiding this comment.
listAll() への切り替えで、このボタンの件数が他ワークスペース分まで混ざっています。
TodoButton は workspaceId を受け取る現在ワークスペース文脈の UI ですが、ここで todoAgent.listAll() を使うと apps/desktop/src/main/todo-agent/trpc-router.ts:99-106 / apps/desktop/src/main/todo-agent/session-store.ts:259-280 の実装どおり全ワークスペースのセッションが返ります。
そのため、別ワークスペースで preparing / running / verifying / queued があるだけでも、このボタンの強調表示とバッジ件数が誤表示になります。ここは従来どおり todoAgent.list({ workspaceId }) を使うか、少なくとも allSessions を workspaceId で絞ってから集計した方がよいです。
修正例
- const { data: allSessions } = electronTrpc.todoAgent.listAll.useQuery(
- undefined,
- { refetchInterval: 3000 },
- );
+ const { data: workspaceSessions } = electronTrpc.todoAgent.list.useQuery(
+ { workspaceId },
+ { refetchInterval: 3000 },
+ );
- const runningCount = (allSessions ?? []).filter(
+ const runningCount = (workspaceSessions ?? []).filter(
(s) =>
s.status === "preparing" ||
s.status === "running" ||
s.status === "verifying",
).length;
- const queuedCount = (allSessions ?? []).filter(
+ const queuedCount = (workspaceSessions ?? []).filter(
(s) => s.status === "queued",
).length;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/renderer/features/todo-agent/TodoButton/TodoButton.tsx`
around lines 30 - 44, TodoButton is using
electronTrpc.todoAgent.listAll.useQuery which returns sessions across all
workspaces, causing incorrect counts; change the data source to only the current
workspace by either calling electronTrpc.todoAgent.list.useQuery with the
current workspaceId or, if keeping listAll, filter allSessions by workspaceId
before computing runningCount/queuedCount; update references to allSessions,
runningCount, queuedCount, and activeCount accordingly so only sessions where
session.workspaceId === workspaceId are included.
| function pairStreamEvents(events: TodoStreamEvent[]): StreamItem[] { | ||
| const items: StreamItem[] = []; | ||
| for (let i = 0; i < events.length; i++) { | ||
| const ev = events[i]; | ||
| if (!ev) continue; | ||
| if (ev.kind === "tool_use") { | ||
| const next = events[i + 1]; | ||
| if (next?.kind === "tool_result") { | ||
| items.push({ | ||
| type: "tool", | ||
| id: ev.id, | ||
| toolUse: ev, | ||
| toolResult: next, | ||
| }); | ||
| i++; | ||
| } else { | ||
| items.push({ type: "tool", id: ev.id, toolUse: ev, toolResult: null }); | ||
| } | ||
| } else if (ev.kind === "tool_result") { | ||
| items.push({ type: "message", id: ev.id, event: ev }); | ||
| } else { | ||
| items.push({ type: "message", id: ev.id, event: ev }); | ||
| } | ||
| } | ||
| return items; | ||
| } |
There was a problem hiding this comment.
連続したツール実行がまだ 1 グループに畳まれていません。
pairStreamEvents() は隣接する tool_use と tool_result を 1 件ずつペアにするだけなので、ツール呼び出しが続く区間でも行数はそのまま増えます。これだと長いツール連打でストリームが依然として縦に伸びますし、tool_result が直後に来ないケースは MessageRow 側へ落ちて表示も崩れます。ここは 1 ペアではなく、連続する tool_use / tool_result をまとめたグループ単位の StreamItem にした方が今回の目的に合っています。
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/desktop/src/renderer/features/todo-agent/TodoManager/TodoManager.tsx`
around lines 1030 - 1055, pairStreamEvents currently pairs only a single
adjacent tool_use with a tool_result; change it to collapse consecutive tool
invocations into one grouped StreamItem (e.g., type "toolGroup") so runs of
tool_use/tool_result become one item. Implement by iterating with an index, and
when you see a tool_use start a group collecting toolUse entries and
corresponding toolResult (if next event is tool_result) in arrays, advancing the
index past consumed events until the next non-tool event; for non-tool events
continue producing message items as before. Update references to StreamItem
consumers (e.g., MessageRow or any renderers expecting single tool entries) to
handle the new group fields (toolUses/toolResults) accordingly.
概要
Issue #192 の対応。AgentManager のライブストリーム表示について 「ToolCall が全部展開されて忙しい」 「VSCode の Claude Code 拡張のようにシンプルにしたい」 という要望。
修正方針
VSCode 拡張と同じく、ツール実行はデフォルト 1 行に畳み、ユーザが気になるときだけ展開する形にした。
スコープ外
Issue 本文で Agent まとめ / VSCode 拡張の実装丸ごと調査 とも書かれているが、現状のストリームは `tool_use` / `tool_result` しか持たず、ツール種別ごとの固有 UI(Edit diff / Read 行番号など) は情報を追加で取り込まないと描けない。今回の PR は 「忙しい」 ことへの即効薬に絞り、固有 UI は別タスクで段階的に足す。
Test plan
Refs #192
Summary by CodeRabbit
リリースノート
New Features
Improvements