fix(claude): emit plan events for TodoWrite during input streaming#1541
fix(claude): emit plan events for TodoWrite during input streaming#1541TimCrooker wants to merge 12 commits intopingdotgg:mainfrom
Conversation
When Claude calls TodoWrite, emit turn.plan.updated events during input streaming so the plan sidebar displays Claude's todos the same way it already works for Codex plan steps. Events are emitted alongside existing tool lifecycle events, not as a replacement. Also passes through the data field on item.completed activities to match item.updated behavior, and auto-opens the plan sidebar when plan steps arrive. Closes pingdotgg#1539
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 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 |
Plan state now falls back to the most recent plan from any previous turn when the current turn has no plan activity, so TodoWrite tasks stay visible across follow-up messages. Simplified redundant isTodoTool check.
There was a problem hiding this comment.
Pull request overview
This PR wires Claude’s TodoWrite tool into the existing plan sidebar by emitting turn.plan.updated events during Claude input streaming, and includes several related UX/data-flow fixes so todos and task activity render consistently in the UI.
Changes:
- Emit
turn.plan.updatedduring Claudeinput_json_deltaprocessing forTodoWrite, without suppressing existing tool lifecycle events. - Persist/restore plan state across turns and auto-open the plan sidebar when plan steps arrive.
- Improve work log rendering (include
task.completed, prefer task payload summaries) and forwarddataforitem.completedactivities.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/session-logic.ts | Persist active plan across turns; include task.completed; adjust task entry label/tone logic. |
| apps/web/src/session-logic.test.ts | Add coverage for plan fallback and updated work log filtering/label behavior. |
| apps/web/src/components/ChatView.tsx | Auto-open plan sidebar when an active plan appears (respecting dismiss state). |
| apps/server/src/provider/Layers/ClaudeAdapter.ts | Emit turn.plan.updated events for TodoWrite during streamed tool input parsing; improve tool request summaries for agent tools. |
| apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts | Forward payload.data for item.completed tool lifecycle activities. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Only force "thinking" tone for task.progress, not task.completed, so failed tasks preserve their error tone. Also check payload.detail for task labels since task.completed stores its summary there. Add regression test for failed task.completed rendering.
Use a sentinel string when turnId is null so the dismissed ref still gets set, preventing the auto-open effect from immediately reopening the sidebar.
Apply the same __dismissed__ sentinel to the onClose handler on the plan sidebar X button, matching the fix already applied to togglePlanSidebar.
Use the same turnKey fallback chain (activePlan.turnId ?? sidebarProposedPlan?.turnId ?? "__dismissed__") in both the auto-open effect and the dismiss handlers so they always match.
Dynamically switch the sidebar label between "Plan" and "Tasks" based on context. When a proposed plan exists or the user is in plan mode, the label reads "Plan". Otherwise it reads "Tasks". Applies to the composer button, compact menu, sidebar badge, and aria labels.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 3 total unresolved issues (including 1 from previous review).
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
…tail Only auto-open the sidebar for plans from the current turn, not fallbacks from previous turns. Add explicit parentheses to the label ternary for clarity. Skip detail assignment when the detail text is already used as the label to avoid duplication in the work log.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Quick summary of the iteration here since there are a few rounds of commits: Started by wiring TodoWrite into the existing plan event path so the sidebar actually shows Claude's tasks. Bugbot caught a few real issues (dismiss key mismatch, error tone override, label/detail duplication, auto-open on thread switch) each one got a focused fix. Midway through I thought this would need a dedicated task UI component separate from the plan sidebar, since the plan panel was designed around Codex's plan mode. Almost closed it to go rethink. Then realized the simpler answer: just dynamically label the sidebar "Tasks" vs "Plan" based on context. Same component, no new UI, no plan mode required. Users get task visibility without being forced into a workflow they didn't ask for. Current state: all Bugbot feedback resolved, tests passing, parentheses fix for the label ternary pushed. Ready for review. |

What Changed
Claude's
TodoWritetool calls now emitturn.plan.updatedevents during input streaming so the plan sidebar shows task progress in real-time. The plan event fires alongside the existing tool lifecycle events, not instead of them.Related fixes:
item.completedactivities now forward thedatafield to the UI, matchingitem.updatedtask.completedentries show up in the work log (previously filtered out withtask.started)payload.summarywhen available instead of the generic activity summaryCloses #1539
Why
TodoWritegets classified asfile_changeinclassifyToolItemTypebecause the name contains "write". It renders as a generic "File change - TodoWrite: {raw JSON}" line in the work log. Noturn.plan.updatedevent gets emitted, so the plan sidebar never activates for Claude sessions. This is core functionality that works for Codex but is completely broken for Claude.There's an existing PR for this (#1387) that intercepts at tool result time and replaces the normal
item.updated/content.deltaemissions. This PR takes a different approach:input_json_deltameans the sidebar populates as Claude writes the input, not after the full round-trip.deriveActivePlanStatefalls back to the most recent plan from any previous turn. Without this, the sidebar clears every time you send a follow-up.Validated with
bun fmt,bun lint,bun typecheck, andbun run test(all passing).UI Changes
Before -- TodoWrite is invisible to the sidebar
Tasks exist but are completely invisible. There is no way to see them.
After -- Tasks stream in live without plan mode
Tasks are now streamed in real-time and persist between turns.
After -- Button label adapts to context
When the session has tasks but no plan, the button shows "Tasks":
When the session is in plan mode, the same button shows "Plan":
Checklist