https://github.com/user-attachments/assets/e72e313c-66f8-40af-bf48-9225422ffa78 ## Problem During a streaming turn with tool calls, the chat goes completely static in two places: - **Between two steps**: once a tool's output arrives, its row flips to past tense and nothing animates until the model's next chunk arrives (a full LLM round trip, often several seconds). This window is defined by the absence of parts, so no part-driven component can fill it — and the pre-turn "…" indicator can't either, since it's cleared on the turn's first chunk and never comes back. - **During tool execution**: the active tool row in `ThinkingStepsDisplay` is a static icon + label; the only animated element there is the orbit loader on an actively-streaming reasoning part. Users can't tell whether the AI chat is still thinking or blocked. ## Fix - **Pending thinking row between steps.** The renderer flags the trailing thinking-steps group of a streaming, error-free message (`showPendingThinkingRow`), and `ThinkingStepsDisplay` appends the thinking row (orbit loader + "Thinking") inside its rows container when none of its own steps is active (`isThinking`, which it already computes). The row occupies the exact slot where the next real step row materializes, so the handoff happens in place with no layout shift. - **One shared row component.** `AiChatThinkingRow` renders the orbit loader + "Thinking" and is used both for an actively-streaming reasoning step and for the pending row. - **Shimmer on executing tools.** Active tool rows wrap their label ("Searching the web for…") in the existing `ShimmeringText` while awaiting output, with the text as a direct child of the background-clip element so the effect applies reliably. - **Activity derived from the tool lifecycle state.** `isThinkingStepPartActive` now checks `input-streaming` / `input-available` instead of output presence, so a tool completing with a legitimate `null` output is no longer classified as still running. Why the trailing-group check is sufficient: anything in progress outside the group — streaming answer text, a running code execution card, a pending question — is itself a later render item, so the group isn't last and never gets flagged. No message-wide part scanning needed. ## Notes - The row renders only while `agentChatIsStreaming`, which the existing keepalive watchdog force-clears (with a visible connection-lost error) after ~5s of subscription silence — it cannot spin forever on a dead stream. - It never shows while waiting on the user: `ask_questions` renders as its own item after the group, and the server ends the stream on that tool anyway (`stopWhen`). - Consciously not covered, for simplicity: a pause right after a mid-turn text part or right after the routing row. ## Tests - Renderer: trailing group flagged as pending while streaming; not flagged when answer text follows or when not streaming - `ThinkingStepsDisplay`: pending row appended after completed steps, suppressed while a tool step runs, loading label shown on a running tool - `isThinkingStepPartActive`: lifecycle-state cases, including a completed tool with `null` output Lint, format, and `typecheck twenty-front` are clean.
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty app:publish --private
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





