[feature][ai-chat] Настраиваемый авто-запуск роли («скилла»): тумблер autoStart + поле «сообщение для запуска» #149

Closed
opened 2026-06-24 05:06:30 +03:00 by Ghost · 0 comments

Кратко

Сейчас «скиллы» (агентные роли, agent roles — карточки ролей в окне AI-чата) всегда запускаются автоматически: клик по карточке роли мгновенно отправляет жёстко зашитое сообщение "Take a look at the current document" и стартует диалог. Нужно сделать это поведение настраиваемым на уровне роли:

  1. Тумблер «Запускать скилл автоматически» (autoStart) — включать/выключать авто-старт диалога при выборе роли.
  2. Поле «Сообщение для запуска» (launchMessage) — настраиваемый текст, который отправляется при авто-старте вместо текущей захардкоженной строки.

Текущее поведение

Карточки ролей рендерятся как empty-state нового чата. По клику роль привязывается к новому чату и сразу отправляется зашитое сообщение:

// apps/client/src/features/ai-chat/components/chat-thread.tsx:322-326
const handleRolePick = (role: IAiRole): void => {
  roleIdRef.current = role.id;
  onRolePicked?.(role);
  sendMessage({ text: t("Take a look at the current document") }); // ← хардкод
};

Проблемы:

  • текст запуска нельзя поменять под конкретную роль (для одной роли уместно «посмотри документ», для другой — «составь план», для третьей — вообще не нужен авто-старт);
  • нельзя выбрать роль, не отправляя сразу сообщение (например, чтобы пользователь сам написал первый запрос).

Желаемое поведение (матрица)

autoStart launchMessage Поведение по клику на карточку роли
true задано привязать роль и сразу отправить launchMessage (новый кастомный текст)
true пусто/null привязать роль и отправить дефолт t("Take a look at the current document") (текущее поведение)
false (любое) только привязать роль, не отправлять сообщение; показать composer с выбранной ролью и фокусом

Дефолты для существующих ролей: autoStart = true, launchMessage = NULL → старое поведение сохраняется без изменений (back-compat).

Объём изменений (full-stack)

1. БД / схема

Новая аддитивная миграция (не править существующую 20260620T120000-ai-agent-roles.ts) — добавить в ai_agent_roles две колонки:

  • auto_start boolean NOT NULL DEFAULT true
  • launch_message text (nullable)

Обновить тип таблицы AiAgentRoles в apps/server/src/database/types/db.d.ts:

autoStart: Generated<boolean>;
launchMessage: string | null;

2. Сервер

  • DTO apps/server/src/core/ai-chat/roles/dto/agent-role.dto.ts — в CreateAgentRoleDto и UpdateAgentRoleDto добавить:
    • autoStart?: boolean (@IsOptional() @IsBoolean());
    • launchMessage?: string (@IsOptional() @IsString() @MaxLength(...), например 2000; обрезка пробелов через @Transform, как у chatModel).
  • Repo apps/server/src/database/repos/ai-agent-roles/ai-agent-roles.repo.ts — пробросить autoStart/launchMessage в insert и в update (с семантикой undefined => не менять, '' => null для launchMessage).
  • Service apps/server/src/core/ai-chat/roles/ai-agent-roles.service.ts:
    • добавить поля в AgentRoleView и в AgentRolePickerView (см. примечание ниже), и в toView/toPickerView;
    • пробросить поля в create/update с нормализацией (пустой launchMessagenull, дефолт autoStart ?? true).

Важно (безопасность/доступ): launchMessage и autoStart должны попасть и в picker-view для обычных участников. В отличие от instructions/modelConfig (только для админов), эти два поля нужны клиенту любого участника, чтобы решить, отправлять ли авто-сообщение и какое. launchMessage отправляется как обычное пользовательское сообщение — это не секрет, утечки промпта роли тут нет.

3. Клиент

  • Типы apps/client/src/features/ai-chat/types/ai-chat.types.ts:
    • IAiRole: autoStart: boolean; launchMessage: string | null;
    • IAiRoleCreate/IAiRoleUpdate: опциональные autoStart?, launchMessage?.
  • Форма роли apps/client/src/features/workspace/components/settings/components/ai-agent-role-form.tsx:
    • Switch «Запускать автоматически» (autoStart);
    • Textarea «Сообщение для запуска» (launchMessage), с подсказкой, что при пустом значении используется дефолт, а при выключенном тумблере поле игнорируется;
    • добавить поля в formSchema, initialValues, эффект ре-гидратации (useEffect по role?.id) и в payload create/update.
  • Логика выбора роли apps/client/src/features/ai-chat/components/chat-thread.tsx (handleRolePick):
    • всегда: roleIdRef.current = role.id; onRolePicked?.(role);
    • если role.autoStart: sendMessage({ text: role.launchMessage?.trim() || t("Take a look at the current document") });
    • если !role.autoStart: ничего не отправлять; перевести empty-state из карточек в composer с выбранной ролью.
    • Нюанс UI: сейчас showRoleCards = chatId === null && roles.length > 0. Если роль выбрана без отправки сообщения, chatId всё ещё null, и карточки не спрячутся. Нужен дополнительный признак «роль выбрана, сообщение ещё не отправлено» (локальный state в chat-thread/окне), чтобы скрыть карточки и показать composer с индикатором выбранной роли. roleIdRef уже выставлен синхронно, поэтому prepareSendMessagesRequest подставит roleId в первый ручной запрос пользователя.

4. i18n

Новые строки UI (label тумблера, label/description поля, подсказки) добавить в словари. Дефолтный текст "Take a look at the current document" оставить как fallback.

Тесты (обновить/добавить)

  • apps/server/src/core/ai-chat/roles/dto/agent-role.dto.spec.ts — валидация autoStart/launchMessage (тип, MaxLength, обрезка пробелов).
  • apps/server/src/core/ai-chat/roles/ai-agent-roles.service.spec.tscreate/update пробрасывают и нормализуют новые поля; дефолт autoStart=true; пустой launchMessage → null.
  • Контракт picker-view (тест дрейфа полей) — убедиться, что autoStart/launchMessage присутствуют для не-админов, а instructions/modelConfig — нет.
  • apps/server/src/database/repos/ai-agent-roles/ai-agent-roles.repo.spec.ts — insert/update новых колонок.
  • Поведение handleRolePick (клиент): авто-старт vs. кастомное сообщение vs. без отправки.
  • Drift-тест драйверов формы трогать не нужно, но проверить, что форма по-прежнему валидна.

Критерии приёмки (DoD)

  • Миграция добавляет auto_start/launch_message; существующие роли работают как раньше (auto-start с дефолтным текстом).
  • В форме роли есть тумблер «Запускать автоматически» и поле «Сообщение для запуска»; значения сохраняются и ре-гидратируются при редактировании.
  • При autoStart=true и заданном launchMessage клик по карточке отправляет именно кастомный текст.
  • При autoStart=true без launchMessage отправляется дефолтная строка (текущее поведение).
  • При autoStart=false клик по карточке только выбирает роль; сообщение не отправляется; composer показывает выбранную роль; роль уходит в roleId первого сообщения пользователя.
  • launchMessage/autoStart отдаются в picker-view обычным участникам; instructions/modelConfig по-прежнему только админам.
  • Тесты обновлены и зелёные; lint/build проходят.

Issue спроектировано по итогам анализа кода в apps/client/src/features/ai-chat и apps/server/src/core/ai-chat/roles.

## Кратко Сейчас «скиллы» (агентные роли, *agent roles* — карточки ролей в окне AI-чата) **всегда** запускаются автоматически: клик по карточке роли мгновенно отправляет жёстко зашитое сообщение `"Take a look at the current document"` и стартует диалог. Нужно сделать это поведение **настраиваемым на уровне роли**: 1. **Тумблер «Запускать скилл автоматически»** (`autoStart`) — включать/выключать авто-старт диалога при выборе роли. 2. **Поле «Сообщение для запуска»** (`launchMessage`) — настраиваемый текст, который отправляется при авто-старте вместо текущей захардкоженной строки. ## Текущее поведение Карточки ролей рендерятся как empty-state нового чата. По клику роль привязывается к новому чату **и сразу** отправляется зашитое сообщение: ```ts // apps/client/src/features/ai-chat/components/chat-thread.tsx:322-326 const handleRolePick = (role: IAiRole): void => { roleIdRef.current = role.id; onRolePicked?.(role); sendMessage({ text: t("Take a look at the current document") }); // ← хардкод }; ``` Проблемы: - текст запуска нельзя поменять под конкретную роль (для одной роли уместно «посмотри документ», для другой — «составь план», для третьей — вообще не нужен авто-старт); - нельзя выбрать роль, *не* отправляя сразу сообщение (например, чтобы пользователь сам написал первый запрос). ## Желаемое поведение (матрица) | `autoStart` | `launchMessage` | Поведение по клику на карточку роли | | ----------- | -------------------------- | -------------------------------------------------------------------------------------------------- | | `true` | задано | привязать роль и сразу отправить `launchMessage` (новый кастомный текст) | | `true` | пусто/`null` | привязать роль и отправить дефолт `t("Take a look at the current document")` (текущее поведение) | | `false` | (любое) | только привязать роль, **не** отправлять сообщение; показать composer с выбранной ролью и фокусом | **Дефолты для существующих ролей:** `autoStart = true`, `launchMessage = NULL` → старое поведение сохраняется без изменений (back-compat). ## Объём изменений (full-stack) ### 1. БД / схема Новая аддитивная миграция (не править существующую `20260620T120000-ai-agent-roles.ts`) — добавить в `ai_agent_roles` две колонки: - `auto_start boolean NOT NULL DEFAULT true` - `launch_message text` (nullable) Обновить тип таблицы `AiAgentRoles` в `apps/server/src/database/types/db.d.ts`: ```ts autoStart: Generated<boolean>; launchMessage: string | null; ``` ### 2. Сервер - **DTO** `apps/server/src/core/ai-chat/roles/dto/agent-role.dto.ts` — в `CreateAgentRoleDto` и `UpdateAgentRoleDto` добавить: - `autoStart?: boolean` (`@IsOptional() @IsBoolean()`); - `launchMessage?: string` (`@IsOptional() @IsString() @MaxLength(...)`, например 2000; обрезка пробелов через `@Transform`, как у `chatModel`). - **Repo** `apps/server/src/database/repos/ai-agent-roles/ai-agent-roles.repo.ts` — пробросить `autoStart`/`launchMessage` в `insert` и в `update` (с семантикой `undefined => не менять`, `'' => null` для `launchMessage`). - **Service** `apps/server/src/core/ai-chat/roles/ai-agent-roles.service.ts`: - добавить поля в `AgentRoleView` **и в `AgentRolePickerView`** (см. примечание ниже), и в `toView`/`toPickerView`; - пробросить поля в `create`/`update` с нормализацией (пустой `launchMessage` → `null`, дефолт `autoStart ?? true`). > **Важно (безопасность/доступ):** `launchMessage` и `autoStart` должны попасть **и в picker-view** для обычных участников. В отличие от `instructions`/`modelConfig` (только для админов), эти два поля нужны клиенту любого участника, чтобы решить, отправлять ли авто-сообщение и какое. `launchMessage` отправляется как обычное пользовательское сообщение — это не секрет, утечки промпта роли тут нет. ### 3. Клиент - **Типы** `apps/client/src/features/ai-chat/types/ai-chat.types.ts`: - `IAiRole`: `autoStart: boolean; launchMessage: string | null;` - `IAiRoleCreate`/`IAiRoleUpdate`: опциональные `autoStart?`, `launchMessage?`. - **Форма роли** `apps/client/src/features/workspace/components/settings/components/ai-agent-role-form.tsx`: - `Switch` «Запускать автоматически» (`autoStart`); - `Textarea` «Сообщение для запуска» (`launchMessage`), с подсказкой, что при пустом значении используется дефолт, а при выключенном тумблере поле игнорируется; - добавить поля в `formSchema`, `initialValues`, эффект ре-гидратации (`useEffect` по `role?.id`) и в payload `create`/`update`. - **Логика выбора роли** `apps/client/src/features/ai-chat/components/chat-thread.tsx` (`handleRolePick`): - всегда: `roleIdRef.current = role.id; onRolePicked?.(role);` - если `role.autoStart`: `sendMessage({ text: role.launchMessage?.trim() || t("Take a look at the current document") });` - если `!role.autoStart`: ничего не отправлять; перевести empty-state из карточек в composer с выбранной ролью. - **Нюанс UI:** сейчас `showRoleCards = chatId === null && roles.length > 0`. Если роль выбрана без отправки сообщения, `chatId` всё ещё `null`, и карточки не спрячутся. Нужен дополнительный признак «роль выбрана, сообщение ещё не отправлено» (локальный state в `chat-thread`/окне), чтобы скрыть карточки и показать composer с индикатором выбранной роли. `roleIdRef` уже выставлен синхронно, поэтому `prepareSendMessagesRequest` подставит `roleId` в первый ручной запрос пользователя. ### 4. i18n Новые строки UI (label тумблера, label/description поля, подсказки) добавить в словари. Дефолтный текст `"Take a look at the current document"` оставить как fallback. ## Тесты (обновить/добавить) - `apps/server/src/core/ai-chat/roles/dto/agent-role.dto.spec.ts` — валидация `autoStart`/`launchMessage` (тип, MaxLength, обрезка пробелов). - `apps/server/src/core/ai-chat/roles/ai-agent-roles.service.spec.ts` — `create`/`update` пробрасывают и нормализуют новые поля; дефолт `autoStart=true`; пустой `launchMessage → null`. - Контракт picker-view (тест дрейфа полей) — убедиться, что `autoStart`/`launchMessage` присутствуют для не-админов, а `instructions`/`modelConfig` — нет. - `apps/server/src/database/repos/ai-agent-roles/ai-agent-roles.repo.spec.ts` — insert/update новых колонок. - Поведение `handleRolePick` (клиент): авто-старт vs. кастомное сообщение vs. без отправки. - Drift-тест драйверов формы трогать не нужно, но проверить, что форма по-прежнему валидна. ## Критерии приёмки (DoD) - [ ] Миграция добавляет `auto_start`/`launch_message`; существующие роли работают как раньше (auto-start с дефолтным текстом). - [ ] В форме роли есть тумблер «Запускать автоматически» и поле «Сообщение для запуска»; значения сохраняются и ре-гидратируются при редактировании. - [ ] При `autoStart=true` и заданном `launchMessage` клик по карточке отправляет именно кастомный текст. - [ ] При `autoStart=true` без `launchMessage` отправляется дефолтная строка (текущее поведение). - [ ] При `autoStart=false` клик по карточке только выбирает роль; сообщение не отправляется; composer показывает выбранную роль; роль уходит в `roleId` первого сообщения пользователя. - [ ] `launchMessage`/`autoStart` отдаются в picker-view обычным участникам; `instructions`/`modelConfig` по-прежнему только админам. - [ ] Тесты обновлены и зелёные; lint/build проходят. --- *Issue спроектировано по итогам анализа кода в `apps/client/src/features/ai-chat` и `apps/server/src/core/ai-chat/roles`.*
Ghost added the feature label 2026-06-24 05:06:30 +03:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#149