ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: hardening Voice Tasker routing, сроков и transcript
This commit is contained in:
@@ -52,11 +52,12 @@ Voice Tasker не создает отдельную модель задачи.
|
||||
|
||||
1. workspace AI settings;
|
||||
2. encrypted workspace credentials;
|
||||
3. voice sessions;
|
||||
4. voice memory.
|
||||
3. voice sessions.
|
||||
|
||||
Не создавать отдельные `VoiceTask`, `VoiceProject`, `VoiceUser`, `VoiceInbox` как обязательные бизнес-сущности.
|
||||
|
||||
Voice memory в MVP не является отдельной бизнес-сущностью и не требует отдельной таблицы. Последние voice-действия восстанавливаются из `voice_task_sessions.created_task_id`, `voice_task_sessions.updated_task_id`, `parsed_json`, `created_at`, `updated_at`. Отдельная `voice_task_memory` допустима только как будущая оптимизация, если session-backed memory перестанет закрывать продуктовый сценарий.
|
||||
|
||||
### 2.2. Backend-only OpenAI
|
||||
|
||||
Frontend:
|
||||
@@ -75,7 +76,7 @@ Backend:
|
||||
- валидирует JSON;
|
||||
- резолвит project/member;
|
||||
- создает/редактирует work item через внутренние Plane модели/serializer/service;
|
||||
- пишет voice session и memory.
|
||||
- пишет voice session и session-backed memory.
|
||||
|
||||
### 2.3. Не использовать внешний Plane REST API
|
||||
|
||||
@@ -148,10 +149,27 @@ Native поля "deadline time" у work item сейчас нет.
|
||||
2. фраза "до 15:00" сохраняется в draft как `due_time`;
|
||||
3. при commit `due_time` не создает новое поле в `Issue`;
|
||||
4. если время важно, оно добавляется в `description_html` отдельной строкой, например: `Ориентир по времени: до 15:00`;
|
||||
5. в `voice_task_sessions.parsed_json` время сохраняется как структурированное значение для будущей миграции.
|
||||
5. относительные сроки вида "на две недели вперед", "на месяц назад", "через два месяца и две недели", "на год перенеси" нормализуются backend rule-based слоем относительно даты пользователя/workspace;
|
||||
6. если задача уже имеет срок и команда звучит как перенос/сдвиг, допустимо считать от текущего `Issue.target_date`;
|
||||
7. в `voice_task_sessions.parsed_json` время сохраняется как структурированное значение для будущей миграции.
|
||||
|
||||
Отдельное native поле дедлайна со временем (`target_datetime`, `due_at` или аналог) не входит в MVP и выносится в backlog как архитектурное расширение.
|
||||
|
||||
### 3.5. Статус задачи
|
||||
|
||||
В текущей модели Plane статус work item хранится через `Issue.state_id` и проектные `State`.
|
||||
|
||||
MVP-правило:
|
||||
|
||||
1. если пользователь явно говорит "в работе", "в реализации", "в реализацию", "активный статус", "в активном статусе" - resolver выбирает state из group `started`;
|
||||
2. если пользователь явно называет статус проекта - resolver делает exact/fuzzy match по имени state;
|
||||
3. если модель не вернула `state_hint`, backend делает ограниченный fallback по явной status-фразе из transcript;
|
||||
4. если статус не назван, backend не должен отдавать выбор случайному default/последнему статусу Plane;
|
||||
5. default для новой voice-задачи: первый `unstarted` state проекта, затем первый `started`, затем backlog только как последний fallback;
|
||||
6. `completed`/`cancelled` допустимы только при явном status/state hint пользователя.
|
||||
|
||||
Цель: voice-create не должен создавать задачи в закрытых/отложенных статусах и не должен попадать в backlog, если в проекте есть нормальный open-state.
|
||||
|
||||
---
|
||||
|
||||
## 4. Пользовательские сценарии
|
||||
@@ -170,6 +188,7 @@ Parser возвращает draft:
|
||||
{
|
||||
"intent": "create_task",
|
||||
"project_hint": "контур бухгалтерии",
|
||||
"state_hint": null,
|
||||
"assignee_hint": "Настя / бухгалтер Настя",
|
||||
"title": "Подготовить декларацию по НДС",
|
||||
"description": "Необходимо подготовить декларацию по НДС.",
|
||||
@@ -202,10 +221,13 @@ Commit маппит draft в Plane:
|
||||
|
||||
MVP-правило:
|
||||
|
||||
1. если `project_confidence >= 0.8`, можно auto-create;
|
||||
2. если проект не найден уверенно, показываем preview с ручным выбором project;
|
||||
3. если admin заранее указал `default_project_id`, можно предложить его как fallback;
|
||||
4. если fallback project не задан, задачу не создаем автоматически.
|
||||
1. backend сначала ищет проект в самом transcript по именам, identifiers и утвержденным алиасам;
|
||||
2. transcript-резолв имеет приоритет над `project_hint`, потому что модель может галлюцинировать текущий проект;
|
||||
3. если `project_hint` найден уверенно и не конфликтует с transcript, можно auto-create;
|
||||
4. если пользователь явно назвал контур/проект, но resolver не нашел его уверенно, запрещено тихо fallback-иться в current/default project;
|
||||
5. если проект не найден уверенно, показываем preview с ручным выбором project;
|
||||
6. если admin заранее указал `default_project_id`, можно использовать его только когда пользователь не называл другой проект явно;
|
||||
7. если fallback project не задан, задачу не создаем автоматически.
|
||||
|
||||
Не создавать "общую помойку" автоматически.
|
||||
|
||||
@@ -235,7 +257,16 @@ MVP-правило:
|
||||
3. показывает preview изменения, если confidence низкий;
|
||||
4. меняет `Issue.target_date`;
|
||||
5. сохраняет `due_time` в description note / parsed JSON;
|
||||
6. пишет новое действие в voice memory.
|
||||
6. пишет новое действие в session-backed memory.
|
||||
|
||||
Если пользователь говорит "переложи последнюю задачу в проект X", это остается `update_task`, но backend должен:
|
||||
|
||||
1. найти target issue через session-backed memory;
|
||||
2. отдельно зарезолвить целевой project из transcript/project hint;
|
||||
3. показать в preview `project_change.from -> project_change.to`;
|
||||
4. при commit перенести обычную `Issue` в целевой `Project`, выдать ей новый `sequence_id` целевого проекта и валидный `State`;
|
||||
5. для фраз "из Бухгалтерии в Менеджмент" считать project после destination-маркера "в/во/на/to" целевым, а project после "из/from" исходным контекстом;
|
||||
6. не считать update успешным, если целевой project не найден или у пользователя нет прав создать/редактировать задачу в целевом проекте.
|
||||
|
||||
### 4.5. Удаление последней voice-задачи
|
||||
|
||||
@@ -249,7 +280,7 @@ MVP-правило:
|
||||
|
||||
- удаление всегда требует confirmation modal;
|
||||
- backend повторно проверяет права на удаление;
|
||||
- действие пишется в voice memory;
|
||||
- действие пишется в session-backed memory;
|
||||
- предпочтительно использовать тот же delete path, что обычный work item, чтобы сохранился activity log.
|
||||
|
||||
---
|
||||
@@ -294,8 +325,8 @@ recording - идет запись
|
||||
uploading - отправка аудио
|
||||
processing - транскрибация и разбор
|
||||
success - draft разобран
|
||||
committing - создание задачи
|
||||
committed - задача создана / обновлена
|
||||
committing - применение voice-действия
|
||||
committed - задача создана / обновлена / удалена
|
||||
error - ошибка
|
||||
```
|
||||
|
||||
@@ -316,7 +347,7 @@ error - ошибка
|
||||
|
||||
Кнопки:
|
||||
|
||||
- `Создать задачу` / `Применить изменения`;
|
||||
- `Создать задачу` / `Применить изменения` / `Удалить задачу`;
|
||||
- `Редактировать`;
|
||||
- `Отмена`.
|
||||
|
||||
@@ -397,7 +428,6 @@ Backend:
|
||||
GET /api/workspaces/:workspaceSlug/voice-task/preflight
|
||||
POST /api/workspaces/:workspaceSlug/voice-task/parse
|
||||
POST /api/workspaces/:workspaceSlug/voice-task/commit
|
||||
POST /api/workspaces/:workspaceSlug/voice-task/resolve-command
|
||||
```
|
||||
|
||||
### 7.1. Preflight
|
||||
@@ -473,6 +503,7 @@ Response:
|
||||
"intent": "create_task",
|
||||
"target_memory_ref": null,
|
||||
"project_hint": "контур бухгалтерии",
|
||||
"state_hint": null,
|
||||
"assignee_hint": "Настя",
|
||||
"title": "Подготовить декларацию по НДС",
|
||||
"description": "Необходимо подготовить декларацию по НДС.",
|
||||
@@ -497,6 +528,13 @@ Response:
|
||||
"confidence": 0.91,
|
||||
"source": "project_hint"
|
||||
},
|
||||
"state": {
|
||||
"id": "state_uuid",
|
||||
"name": "К выполнению",
|
||||
"group": "unstarted",
|
||||
"confidence": 0.65,
|
||||
"source": "default_open_state"
|
||||
},
|
||||
"assignee": {
|
||||
"id": "user_uuid",
|
||||
"name": "Настя",
|
||||
@@ -510,6 +548,7 @@ Response:
|
||||
"name": "voice"
|
||||
}
|
||||
],
|
||||
"target_task": null,
|
||||
"warnings": [],
|
||||
"can_commit": true
|
||||
},
|
||||
@@ -536,11 +575,12 @@ Payload:
|
||||
```json
|
||||
{
|
||||
"voice_session_id": "uuid",
|
||||
"action": "create_task",
|
||||
"action": "create_task | update_task | delete_task",
|
||||
"draft": {
|
||||
"title": "Подготовить декларацию по НДС",
|
||||
"description": "Необходимо подготовить декларацию по НДС.",
|
||||
"project_id": "project_uuid",
|
||||
"state_hint": null,
|
||||
"assignee_ids": ["user_uuid"],
|
||||
"due_date": "2026-04-24",
|
||||
"due_time": "15:00",
|
||||
@@ -558,6 +598,7 @@ Internal Plane payload:
|
||||
"description_html": "<p>Необходимо подготовить декларацию по НДС.</p><p><strong>Ориентир по времени:</strong> до 15:00</p>",
|
||||
"target_date": "2026-04-24",
|
||||
"priority": "high",
|
||||
"state_id": "state_uuid",
|
||||
"assignees": ["user_uuid"],
|
||||
"labels": ["label_uuid"]
|
||||
}
|
||||
@@ -583,6 +624,13 @@ Response:
|
||||
"confidence": 0.91,
|
||||
"source": "project_hint"
|
||||
},
|
||||
"state": {
|
||||
"id": "state_uuid",
|
||||
"name": "К выполнению",
|
||||
"group": "unstarted",
|
||||
"confidence": 0.65,
|
||||
"source": "default_open_state"
|
||||
},
|
||||
"assignee": {
|
||||
"id": "user_uuid",
|
||||
"name": "Настя",
|
||||
@@ -591,13 +639,20 @@ Response:
|
||||
"source": "assignee_hint"
|
||||
},
|
||||
"labels": [{ "id": "label_uuid", "name": "voice" }],
|
||||
"target_task": null,
|
||||
"warnings": [],
|
||||
"can_commit": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
На Stage 4 commit поддерживает только `create_task`. `update_task` и `delete_task` остаются в следующих этапах, потому что требуют voice memory и отдельного confirmation policy.
|
||||
Commit поддерживает:
|
||||
|
||||
- `create_task` - создает обычную `Issue`;
|
||||
- `update_task` - находит target issue через session-backed memory и применяет частичный update;
|
||||
- `delete_task` - находит target issue через session-backed memory и удаляет через тот же delete path, что обычный work item.
|
||||
|
||||
Для `update_task`/`delete_task` response содержит `resolution.target_task` с ключом, названием, проектом и источником резолва.
|
||||
|
||||
---
|
||||
|
||||
@@ -675,28 +730,32 @@ Audio file в MVP не хранить после обработки.
|
||||
|
||||
Transcript и parsed JSON хранить для поддержки preview, отладки и memory. Retention policy нужно вынести в отдельную настройку после MVP.
|
||||
|
||||
### 8.4. `voice_task_memory`
|
||||
### 8.4. Session-backed voice memory
|
||||
|
||||
Поля:
|
||||
В MVP отдельная таблица `voice_task_memory` не создается.
|
||||
|
||||
Источник memory:
|
||||
|
||||
```txt
|
||||
id
|
||||
workspace_id
|
||||
user_id
|
||||
task_id
|
||||
voice_session_id
|
||||
action
|
||||
summary
|
||||
created_at
|
||||
voice_task_sessions.workspace_id
|
||||
voice_task_sessions.user_id
|
||||
voice_task_sessions.intent
|
||||
voice_task_sessions.parsed_json
|
||||
voice_task_sessions.created_task_id
|
||||
voice_task_sessions.updated_task_id
|
||||
voice_task_sessions.created_at
|
||||
voice_task_sessions.updated_at
|
||||
```
|
||||
|
||||
Использование:
|
||||
|
||||
- хранить последние N voice-действий пользователя;
|
||||
- N по умолчанию: 10;
|
||||
- N по умолчанию для parser context: 5;
|
||||
- резолвить "последняя задача", "предыдущая задача", "та задача в бухгалтерии";
|
||||
- не использовать memory как источник истины вместо `Issue`.
|
||||
|
||||
Если в будущем понадобится отдельная агрегированная history/memory-таблица, она должна быть добавлена отдельным архитектурным этапом и не должна дублировать `Issue` как источник истины.
|
||||
|
||||
---
|
||||
|
||||
## 9. OpenAI pipeline
|
||||
@@ -741,7 +800,26 @@ Input:
|
||||
"transcript": "...",
|
||||
"workspace_projects": [],
|
||||
"workspace_members": [],
|
||||
"recent_voice_memory": [],
|
||||
"recent_voice_memory": [
|
||||
{
|
||||
"voice_session_id": "uuid",
|
||||
"intent": "create_task",
|
||||
"title": "Подготовить декларацию по НДС",
|
||||
"project_hint": "Бухгалтерия",
|
||||
"target_task": {
|
||||
"id": "issue_uuid",
|
||||
"title": "Подготовить декларацию по НДС",
|
||||
"key": "BUH-128",
|
||||
"project_id": "project_uuid",
|
||||
"project_name": "Бухгалтерия",
|
||||
"project_identifier": "BUH",
|
||||
"sequence_id": 128,
|
||||
"source": "recent_voice_memory",
|
||||
"voice_session_id": "uuid"
|
||||
},
|
||||
"created_at": "2026-04-24T10:00:00+03:00"
|
||||
}
|
||||
],
|
||||
"current_date": "2026-04-24",
|
||||
"timezone": "Europe/Moscow"
|
||||
}
|
||||
@@ -752,7 +830,7 @@ Output строго JSON:
|
||||
```json
|
||||
{
|
||||
"intent": "create_task | update_task | delete_task | unknown",
|
||||
"target_memory_ref": "last_task | previous_task | explicit_task | null",
|
||||
"target_memory_ref": "voice_session_id | issue_key | issue_id | null",
|
||||
"project_hint": "string | null",
|
||||
"assignee_hint": "string | null",
|
||||
"title": "string | null",
|
||||
@@ -789,20 +867,37 @@ Return JSON only.
|
||||
Вход:
|
||||
|
||||
- `project_hint`;
|
||||
- transcript;
|
||||
- список проектов workspace, доступных пользователю;
|
||||
- `current_project_id`, если пользователь находится внутри проекта;
|
||||
- `default_project_id` из settings.
|
||||
|
||||
Логика:
|
||||
|
||||
1. exact match по имени/identifier;
|
||||
2. fuzzy match по имени;
|
||||
3. current project как слабый fallback;
|
||||
4. default project как fallback, если задан;
|
||||
5. если confidence низкий - preview с ручным выбором.
|
||||
1. exact/alias match по transcript: имя проекта, identifier, короткие алиасы (`MGR`, `BUH`, `CODEX`) и согласованные русские UX-формы вроде "контур менеджмент";
|
||||
2. transcript match имеет приоритет над `project_hint`;
|
||||
3. exact/fuzzy match по `project_hint` и candidates проекта;
|
||||
4. current project как слабый fallback только если пользователь не назвал проект/контур явно;
|
||||
5. default project как fallback только если пользователь не назвал проект/контур явно;
|
||||
6. если confidence низкий - preview с ручным выбором;
|
||||
7. если transcript содержит явную маршрутизацию проекта, но model `project_hint` указывает на проект, которого нет в transcript, auto-commit блокируется;
|
||||
8. при наличии source/destination фразы выбирать destination project: "из Бухгалтерии в Менеджмент" -> target `Менеджмент`, "из Менеджмента в Бухгалтерию" -> target `Бухгалтерия`.
|
||||
|
||||
Не зашивать термин "контур" как обязательный. Для NODE DC это важный UX-термин, но технически это обычный `Project`.
|
||||
|
||||
### 10.1.1. Перенос задачи между проектами
|
||||
|
||||
Команда вида "переложи последнюю задачу в проект Менеджмент" не создает новую задачу.
|
||||
|
||||
Правило:
|
||||
|
||||
1. target issue находится через session-backed memory;
|
||||
2. целевой project резолвится тем же project resolver, но без current/default fallback;
|
||||
3. если целевой project отличается от исходного, response содержит `resolution.project_change`;
|
||||
4. commit переносит `Issue.project_id`, выдает новый `sequence_id` целевого проекта через `IssueSequence`, выбирает state целевого проекта по явному `state_hint`, group исходного state или default open-state;
|
||||
5. assignees/labels сохраняются только если они валидны в целевом project;
|
||||
6. при неуверенном project resolver или недостатке прав перенос не выполняется.
|
||||
|
||||
### 10.2. Assignee resolver
|
||||
|
||||
Вход:
|
||||
@@ -820,15 +915,60 @@ Return JSON only.
|
||||
|
||||
Назначать можно только пользователей, которые состоят в project и имеют достаточную роль.
|
||||
|
||||
### 10.3. Date resolver
|
||||
### 10.3. State resolver
|
||||
|
||||
Вход:
|
||||
|
||||
- `state_hint`;
|
||||
- states выбранного project;
|
||||
- transcript как fallback, если модель не вернула `state_hint`;
|
||||
- group state: `backlog`, `unstarted`, `started`, `completed`, `cancelled`.
|
||||
|
||||
Логика:
|
||||
|
||||
1. exact/fuzzy match по имени state;
|
||||
2. словари синонимов для group:
|
||||
- "в работе", "в реализации", "в реализацию", "активный", "активном статусе" -> `started`;
|
||||
- "к выполнению", "todo", "новая" -> `unstarted`;
|
||||
- "бэклог", "backlog" -> `backlog`;
|
||||
- "готово", "закрыто" -> `completed`;
|
||||
3. если `state_hint` отсутствует, но transcript содержит явную status-фразу, использовать ее как backend fallback;
|
||||
4. если `state_hint` отсутствует при создании - выбрать default open-state: сначала `unstarted`, затем `started`, затем backlog;
|
||||
5. не выбирать `completed`/`cancelled` без явного state hint пользователя.
|
||||
|
||||
### 10.4. Date resolver
|
||||
|
||||
MVP:
|
||||
|
||||
- сегодня;
|
||||
- завтра;
|
||||
- послезавтра / вчера / позавчера;
|
||||
- `N` дней/недель/месяцев/лет вперед;
|
||||
- `N` дней/недель/месяцев/лет назад;
|
||||
- сложные интервалы: "два месяца и две недели";
|
||||
- числительные цифрами и частые русские словоформы: "2 недели", "две недели", "пару дней";
|
||||
- защита от ложных матчей внутри слов: "последней" не считается как "дней";
|
||||
- конкретная дата;
|
||||
- конкретное время как `due_time` note.
|
||||
|
||||
Date resolver обязан работать после OpenAI parser как deterministic fallback. Если модель уже вернула валидный `due_date`, backend его не переписывает.
|
||||
|
||||
### 10.5. Voice task representation in Issue
|
||||
|
||||
Voice-created/voice-updated work item остается обычной `Issue`, но получает техническую маркировку:
|
||||
|
||||
- `Issue.external_source = voice_tasker`;
|
||||
- `Issue.external_id = voice_session_id`.
|
||||
|
||||
Описание voice-created задачи формируется многоуровнево:
|
||||
|
||||
1. источник `Voice Tasker`;
|
||||
2. подробная постановка из parser `description`;
|
||||
3. декомпозиция из `checklist`, если она есть;
|
||||
4. исходная транскрибация пользователя отдельным блоком `Исходная транскрибация пользователя`.
|
||||
|
||||
Kanban/list UI может использовать `external_source=voice_tasker` для отличимого отображения voice-задач без создания отдельной бизнес-сущности.
|
||||
|
||||
Backlog:
|
||||
|
||||
- к пятнице;
|
||||
@@ -927,7 +1067,7 @@ MVP:
|
||||
- хранить transcript N дней;
|
||||
- очищать transcript после commit;
|
||||
- хранить только parsed JSON;
|
||||
- отключать voice memory для sensitive workspace.
|
||||
- отключать session-backed voice memory для sensitive workspace.
|
||||
|
||||
---
|
||||
|
||||
@@ -991,6 +1131,7 @@ voice_task.error
|
||||
### Stage 4 - Preview и создание задачи
|
||||
|
||||
- project resolver;
|
||||
- state resolver и безопасный default open-state;
|
||||
- assignee resolver;
|
||||
- date resolver MVP;
|
||||
- preview modal;
|
||||
@@ -998,14 +1139,19 @@ voice_task.error
|
||||
- создание `Issue` через внутренний Plane layer;
|
||||
- activity log/model activity как у обычного work item;
|
||||
- точечное обновление активного issue-store после commit без reload/polling;
|
||||
- `voice_task_memory` для created action.
|
||||
- session-backed memory для created action.
|
||||
|
||||
### Stage 5 - Memory commands
|
||||
|
||||
- update last task;
|
||||
- delete last task with confirmation;
|
||||
- append description/checklist to last task;
|
||||
- memory resolver для "последняя/предыдущая/та задача".
|
||||
- memory resolver для "последняя/предыдущая/та задача";
|
||||
- transcript-first project routing и базовые project aliases;
|
||||
- перенос последней voice-задачи между проектами через обычную `Issue`.
|
||||
- относительные сроки русским естественным языком;
|
||||
- маркировка voice-задач через `Issue.external_source`;
|
||||
- сохранение полного transcript в description_html созданной/обновленной задачи.
|
||||
|
||||
---
|
||||
|
||||
@@ -1017,7 +1163,7 @@ voice_task.error
|
||||
- Voice Inbox как отдельный управляемый fallback project/triage flow;
|
||||
- Redis/Celery-backed VoiceTaskQueue;
|
||||
- transcript retention policies;
|
||||
- project/member aliases;
|
||||
- расширяемые project/member aliases в настройках workspace;
|
||||
- выбранные роли beyond `all members/admins only`;
|
||||
- monthly budget/soft cap;
|
||||
- multi-provider AI;
|
||||
@@ -1039,18 +1185,22 @@ voice_task.error
|
||||
7. Backend транскрибирует через OpenAI.
|
||||
8. Backend формирует валидный structured draft.
|
||||
9. Project resolver выбирает project или требует ручной выбор.
|
||||
10. Assignee resolver назначает только уверенно найденного project member.
|
||||
11. Если assignee не найден - задача может быть создана без assignee.
|
||||
12. Commit создает обычную `Issue` через внутренний Plane backend layer.
|
||||
13. После commit активная доска/список обновляется без reload страницы.
|
||||
14. `due_date` маппится в `target_date`.
|
||||
15. `due_time` сохраняется как note в description и parsed JSON, без новой колонки в MVP.
|
||||
15. Voice session сохраняется.
|
||||
16. Последняя voice-задача сохраняется в memory.
|
||||
17. Update last task работает минимум для `target_date` и description.
|
||||
18. Delete last task требует confirmation.
|
||||
19. User/workspace limits работают.
|
||||
20. OpenAI errors показываются пользователю как нормальные сообщения без raw stack trace.
|
||||
10. State resolver выбирает явный state или безопасный default open-state.
|
||||
11. Assignee resolver назначает только уверенно найденного project member.
|
||||
12. Если assignee не найден - задача может быть создана без assignee.
|
||||
13. Commit создает обычную `Issue` через внутренний Plane backend layer.
|
||||
14. После commit активная доска/список обновляется без reload страницы.
|
||||
15. `due_date` маппится в `target_date`.
|
||||
16. `due_time` сохраняется как note в description и parsed JSON, без новой колонки в MVP.
|
||||
17. Voice session сохраняется.
|
||||
18. Последняя voice-задача сохраняется в session-backed memory.
|
||||
19. Update last task работает минимум для `target_date`, state и description.
|
||||
20. Delete last task требует confirmation.
|
||||
21. User/workspace limits работают.
|
||||
22. OpenAI errors показываются пользователю как нормальные сообщения без raw stack trace.
|
||||
23. Относительная дата "на две недели вперед" меняет `Issue.target_date` без ручного ввода ISO-даты.
|
||||
24. Перенос "из Бухгалтерии в Менеджмент" и "из Менеджмента в Бухгалтерию" реально меняет `Issue.project_id` и `task_key`.
|
||||
25. Voice-created task имеет `external_source=voice_tasker` и хранит исходный transcript в description.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user