# fix\(clients\): reconcile tool activity lifecycles · PR \#7617 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/7617)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: closed

Source repository: pingdotgg/t3code

Source branch: ui-refresh/tool-activity-turns

Target branch: main

Head commit: befa5111b6eddbd887b2387f0837058293f4bae4

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/7617)

[Commits](https://git.cafe/pingdotgg/t3code/pull/7617/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/7617/diffs?format=markdown)

## Description

---

## What changed

Moves the deeper behavior out of #7152 into a follow-up layer:

- reconciles interleaved tool lifecycle events by stable call ID without merging distinct or ambiguous calls
- preserves terminal outcomes and turn-scoped identity on web and mobile
- keeps live tool rows in timeline order and updates settled turn-fold anchoring
- filters duplicate plan and terminal-input activity from work history
- preserves composer task progress after removing the old active-plan fallback

## Why

The one-line tool UI can be reviewed and merged independently. This PR contains the turn, lifecycle, and mobile behavior needed for complete provider histories.

## Validation

- 197 focused web and mobile tests
- Web and mobile typechecks
- Changed-file lint and formatting checks

Built with GPT-5.6-sol in the Codex harness.

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Reconcile tool activity lifecycles by `toolCallId` and overhaul `MessagesTimeline` rows
> - Tool lifecycle entries (`tool.started`, `tool.updated`, `tool.completed`) now collapse by stable `toolCallId` scoped to a turn, preventing cross-turn merges and ambiguous id-less completions. Applies to both [session-logic.ts](https://github.com/pingdotgg/t3code/pull/7617/files#diff-9f1f9f19555f14c3c47d4ce52e1b7dcec7e968e54f400820085376797a7223d6) and [threadActivity.ts](https://github.com/pingdotgg/t3code/pull/7617/files#diff-a18e8ed6aa4a9d6b3d9e32ea2fa3034dfd7621b6af3d640ec1b95a8564c97eb5).
> - `deriveWorkLogEntries` now surfaces `tool.started` rows (previously blanket-skipped), filters Codex terminal interactions and unkeyed statusless starts, and infers `inProgress`/`completed` statuses when missing.
> - Timeline introduces new row kinds: `work-live` for active expandable tool batches, enriched `work-toggle` with summaries and failure icons, and `working` rows with a `showThinking` flag. Turn folds anchor near the assistant response when collapsed and above the first hidden entry when expanded.
> - New shell tokenizer (`tokenizeShellCommand`, `commandProgramName`) extracts program names for live command labels; `workEntryIconName` delegates to `toolGroupAction` for icon selection.
> - `deriveActivePlanState` removed; `activeComposerTaskSteps` now sourced from `turnPlans` for the active turn. `MessagesTimeline` no longer accepts `workingStepLabel` or `activeTurnInProgress` props.
> - Risk: `MessagesTimeline` prop interface removes `workingStepLabel` and `activeTurnInProgress`; any external callers passing these props will break. `deriveActiveWorkStartedAt` signature gains `latestUserMessageAt` (defaults to `null`), and `deriveActivePlanState` is deleted from [session-logic.ts](https://github.com/pingdotgg/t3code/pull/7617/files#diff-9f1f9f19555f14c3c47d4ce52e1b7dcec7e968e54f400820085376797a7223d6).
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized befa511.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes are concentrated in client-side timeline and work-log derivation with broad test coverage, but incorrect collapse or fold anchoring could misrepresent tool history or hide activity in edge cases (parallel id-less tools, steers).
> 
> **Overview**
> **Tool lifecycle reconciliation** on web and mobile collapses `tool.started` / `tool.updated` / `tool.completed` by `turnId` + `toolCallId`, including interleaved and late events, while refusing to merge ambiguous id-less completions or reuse IDs across turns. Terminal outcomes (completed, failed, etc.) win over stale in-progress updates.
> 
> **Work log and timeline presentation** now surfaces in-flight `tool.started` rows, filters duplicate plan updates, unkeyed statusless starts, Codex PTY terminal-input noise, and `ExitPlanMode` markers. Settled **turn folds** anchor before the next visible assistant reply when collapsed (fixes steer-superseded turns) and before the first hidden entry when expanded. Live tool batches stay expandable as entries append; lifecycle marker filtering and `summarizeToolGroup` labeling improve settled overflow rows.
> 
> **Web chat** drops `deriveActivePlanState` / `workingStepLabel`; composer task steps come from `turnPlans` for the active turn. The working row no longer shows plan step text. **Subagent spawn** UI shows coordinator failure states and uses a compact card with an Eye action instead of a full-width button.
> 
> **Failure display** splits metadata vs rendered output via `workEntryDisplayIndicatesToolFailure` so command text like `rg 'command not found'` does not show a false failure while still running.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit befa5111b6eddbd887b2387f0837058293f4bae4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
