AI-чат: индикатор прогресса выполнения инструмента в UI (elapsed / timeout, напр. «40с / 120с») #404

Closed
opened 2026-07-07 04:20:29 +03:00 by agent_vscode · 0 comments
Collaborator

Контекст

Автономные раны (роль-исследователь) зовут внешние 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-инструмента показывать:

  • прошедшее время и лимит: «40с / 120с»;
  • тонкий прогресс-бар, заполняющийся к лимиту;
  • цвет: нейтральный → янтарный (>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

  • Рендер tool-part: 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 + прогресс-бар.
  • Один общий тикающий clock-хук (1с) на все активные вызовы, а не N таймеров; сбрасывать по output-available/error и на unmount.
  • Несколько параллельных вызовов — независимо (ключ toolCallId).
  • Не завязывать на experimental_throttle/token-стрим: отдельный лёгкий 1с-интервал.
  • message-signature.ts: НЕ тащить сюда input-дельты (см. существующее предупреждение в tool-parts.tsx); тик времени держать в локальном стейте строки, чтобы не инвалидировать мемоизацию всего транскрипта.

Затрагиваемые файлы

  • Backend: 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).
  • Frontend: ai-chat/utils/tool-parts.tsx, ai-chat/components/message-item.tsx, новый маленький «tick»-хук, i18n-ключи.

Acceptance criteria

  • Для выполняющегося внешнего MCP-инструмента виден счётчик «Nс / <лимит>с» и прогресс-бар, обновление ~раз в 1с.
  • Индикатор исчезает при завершении; при таймауте part → error.
  • Для внутренних инструментов индикатора нет.
  • Несколько параллельных вызовов показываются независимо.
  • Переживает reconnect/resume (вариант B — точное время; A — допускается сброс).
  • Нет утечек таймеров и лишних ре-рендеров всего транскрипта.

Вне scope

  • Изменение самих значений таймаутов (сделано отдельно: per-call 120с, silence 60с, + Fastify bodyLimit 25 МиБ против 413).
  • Кнопка отмены конкретного вызова (возможное продолжение).
## Контекст Автономные раны (роль-исследователь) зовут внешние 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-инструмента показывать: - прошедшее время и лимит: «40с / 120с»; - тонкий прогресс-бар, заполняющийся к лимиту; - цвет: нейтральный → янтарный (~>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 - Рендер tool-part: `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` + прогресс-бар. - Один общий тикающий clock-хук (1с) на все активные вызовы, а не N таймеров; сбрасывать по `output-available`/`error` и на unmount. - Несколько параллельных вызовов — независимо (ключ `toolCallId`). - Не завязывать на `experimental_throttle`/token-стрим: отдельный лёгкий 1с-интервал. - `message-signature.ts`: НЕ тащить сюда `input`-дельты (см. существующее предупреждение в `tool-parts.tsx`); тик времени держать в локальном стейте строки, чтобы не инвалидировать мемоизацию всего транскрипта. ## Затрагиваемые файлы - Backend: `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). - Frontend: `ai-chat/utils/tool-parts.tsx`, `ai-chat/components/message-item.tsx`, новый маленький «tick»-хук, i18n-ключи. ## Acceptance criteria - [ ] Для выполняющегося внешнего MCP-инструмента виден счётчик «Nс / `<лимит>`с» и прогресс-бар, обновление ~раз в 1с. - [ ] Индикатор исчезает при завершении; при таймауте part → error. - [ ] Для внутренних инструментов индикатора нет. - [ ] Несколько параллельных вызовов показываются независимо. - [ ] Переживает reconnect/resume (вариант B — точное время; A — допускается сброс). - [ ] Нет утечек таймеров и лишних ре-рендеров всего транскрипта. ## Вне scope - Изменение самих значений таймаутов (сделано отдельно: per-call 120с, silence 60с, + Fastify `bodyLimit` 25 МиБ против 413). - Кнопка отмены конкретного вызова (возможное продолжение).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#404