AI-чат: индикатор прогресса выполнения инструмента в UI (elapsed / timeout, напр. «40с / 120с») #404
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Контекст
Автономные раны (роль-исследователь) зовут внешние MCP-инструменты (напр.
Search_web_search). Каждый такой вызов ограничен серверным per-call таймаутомmcpCallTimeoutMs()(после снижения — 120 000 мс; было 900 000). См.wrapToolWithCallTimeoutвapps/server/src/core/ai-chat/external-mcp/mcp-clients.service.tsи константы вapps/server/src/integrations/ai/ai-streaming-fetch.ts.Сейчас, пока инструмент выполняется, в UI крутится безликий спиннер — не видно, сколько он уже работает и сколько осталось до аборта по таймауту. При зависшем инструменте пользователь не понимает «оно живое или повисло» и ждёт вслепую (реальный кейс: вызов висел ~15 мин до старого таймаута).
Что хотим (UX)
Под строкой «Ran tool
<name>» для выполняющегося внешнего MCP-инструмента показывать:>70%) → красный (>90%);output-available) индикатор исчезает; по таймауту part становитсяerrorс уже существующим текстомMCP tool call timed out after 120000ms.Только для инструментов, у которых реально есть этот таймаут (внешние MCP). Внутренние Docmost-инструменты (
searchPages,getPage, …) НЕ оборачиваются таймаутом — для них индикатор не показываем.Техдизайн
Клиент сейчас не знает ни значения таймаута, ни момента старта вызова, ни того, какие инструменты внешние-MCP. Это нужно пробросить. Два варианта, рекомендую (B):
(A) MVP, client-local
Клиент засекает
Date.now()в момент первого рендера part в состоянииrunning(input-streaming/input-available), тикает раз в 1с. Значение лимита отдать один раз через bootstrap/workspaceAtom(напр.settings.ai.mcpCallTimeoutMs, read-only, производное отmcpCallTimeoutMs()). Список внешних-MCP инструментов — тоже отдать/пометить.Минусы: старт неточный (время рендера ≠ старт вызова на сервере), сбрасывается при reconnect/resume.
(B) Рекомендуемый, server-authoritative
На старте вызова внешнего MCP-инструмента сервер эмитит кастомный SSE data-part
data-mcp-tool-timerс{ toolCallId, startedAt, timeoutMs }— естественно только для инструментов, обёрнутых таймаутом (точка входа уже есть —executeвwrapToolWithCallTimeout). Клиент матчит его к tool-part поtoolCallIdи показываетnow - startedAt/timeoutMs.Плюсы: точный старт; переживает resume (реестр стримов реплеит data-part); сам факт события = «инструмент под таймаутом», без отдельного списка/эвристики.
Frontend
apps/client/src/features/ai-chat/components/message-item.tsx+ хелперыapps/client/src/features/ai-chat/utils/tool-parts.tsx. ДляtoolRunState(part) === "running"и наличия таймера — рисоватьelapsed / timeout+ прогресс-бар.output-available/errorи на unmount.toolCallId).experimental_throttle/token-стрим: отдельный лёгкий 1с-интервал.message-signature.ts: НЕ тащить сюдаinput-дельты (см. существующее предупреждение вtool-parts.tsx); тик времени держать в локальном стейте строки, чтобы не инвалидировать мемоизацию всего транскрипта.Затрагиваемые файлы
external-mcp/mcp-clients.service.ts(эмит data-part при старте вызова), stream-частьcore/ai-chat/ai-chat.service.ts/ контроллер (проброс),integrations/ai/ai-streaming-fetch.ts(значение таймаута), workspace settings/bootstrap (для варианта A).ai-chat/utils/tool-parts.tsx,ai-chat/components/message-item.tsx, новый маленький «tick»-хук, i18n-ключи.Acceptance criteria
<лимит>с» и прогресс-бар, обновление ~раз в 1с.Вне scope
bodyLimit25 МиБ против 413).