bitrix24-mcp-bridge 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/api-notes.md CHANGED
@@ -1,44 +1,34 @@
1
1
  # Bitrix24 API notes (reverse-engineered)
2
2
 
3
- Справочник по внутреннему API Bitrix24, добытый **живым реверсом** (у внутренних ajax-контроллеров
4
- нет публичной документации). Предназначен для любого ИИ-агента или разработчика, который вызывает
5
- эти методы через мост (`bitrix_call` / типизированные инструменты) или расширяет каталог.
3
+ Как агенту работать с Bitrix24 через этот мост: формат `params`, имена полей, готовые цепочки вызовов.
6
4
 
7
- Наблюдения сняты на портале `example.bitrix24.ru` (облако, мессенджер **im.v2**, задачи в режиме
8
- **scrum-борда**). Имена полей и конвенции стандартны для облачного Bitrix24, но при переносе на другой
9
- портал сверяйся реверсом (`docs/reconnaissance.md`).
5
+ Две поверхности API. **Публичный REST** (`tasks.task.*`, `im.recent.list`, `im.user.get`, `im.chat.get`,
6
+ `socialnetwork.api.workgroup.*`) — есть официальные доки, ссылки на них помечены **📖**
7
+ ([`apidocs.bitrix24.com`](https://apidocs.bitrix24.com); агенту с MCP context7 — библиотека
8
+ `/bitrix24/b24restdocs`). **Внутренние ajax-контроллеры** (`im.v2.*`, `tasks.v2.*`, `ui.entityselector.*`)
9
+ публичных доков не имеют — всё ниже добыто живым реверсом; конверт и регистр полей у них могут
10
+ отличаться от 📖-аналогов. Имена полей стандартны для облачного Bitrix24; на другом портале сверяйся
11
+ реверсом (`docs/reconnaissance.md`).
10
12
 
11
13
  ---
12
14
 
13
15
  ## 1. Как вызывать
14
16
 
15
- - **`bitrix_call { name, params }`** — универсальный вызов любого разрешённого имени из каталога
16
- (`actions.json`). `params` — нативный формат Bitrix (см. раздел 3). Каталог может включать
17
- мутирующие вызовы; allowlist (`actions.json`) — единственная граница, не режим чтения.
18
- - **Типизированные инструменты** — обёртки с разумными дефолтами над каталогом. Точная выборка — через
19
- опциональный `params`, он мержится **последним** и перекрывает дефолты. Регистрируются только те,
20
- чьё имя есть в каталоге (`actions.json`); иначе инструмент пропускается. Текущий набор:
21
- - Задачи: `bitrix_tasks_list`, `bitrix_task_get`, `bitrix_task_get_v2` (v2/JSON),
22
- `bitrix_task_scrum_info`, `bitrix_task_files`, `bitrix_task_views_count`,
23
- `bitrix_task_subtasks` (подзадачи), `bitrix_task_related` (связанные).
24
- - Проекты: `bitrix_projects_list`, `bitrix_project_get`.
25
- - Чаты: `bitrix_chats_recent`, `bitrix_recent_load` (недавние по секции — в т.ч. `tasksTask` =
26
- чаты задач), `bitrix_recent_tail` (листать недавние вглубь), `bitrix_chat_load` (открыть по
27
- `dialogId`/`chatId`), `bitrix_chat_messages`, `bitrix_chat_history` (листать вглубь по `beforeId`),
28
- `bitrix_chat_get_dialog_id` (dialogId по externalId), `bitrix_chat_mark_read` (⚠ мутирующий),
29
- `bitrix_chat_read_all` (⚠ мутирующий).
30
- - Люди/поиск: `bitrix_user_get` (карточка юзера), `bitrix_entity_selector` (загрузка селектора),
31
- `bitrix_entity_search` (текстовый поиск чатов/сущностей через `ui.entityselector.doSearch`).
32
-
33
- Любое имя каталога всегда доступно и напрямую через `bitrix_call { name, params }`.
34
- - **`bitrix_help`** — этот же гайд, отдаётся через MCP (инструмент + resource `bitrix://api-notes`).
35
- Источник — **этот файл** (`docs/api-notes.md`).
36
-
37
- Полный список имён каталога отдаёт `bitrix_call` в своём описании (`deps.catalog.names()`).
17
+ - **Типизированные инструменты** (`bitrix_tasks_list`, `bitrix_task_get`, `bitrix_chat_load`, …) —
18
+ обёртки с разумными дефолтами; их полный список и описания видны среди MCP-инструментов. Точная
19
+ выборка — опциональным `params`: он мержится **последним** и перекрывает дефолты.
20
+ - **`bitrix_call { name, params }`** — любое имя из каталога (`actions.json`), `params` в нативном
21
+ формате Bitrix (§3). Полный список имён отдаёт сам `bitrix_call` в своём описании.
22
+ - Каталог может содержать **мутирующие** вызовы: наличие записи = разрешение вызвать. Это не режим
23
+ «только чтение».
24
+ - У обёрток есть дефолты (напр. `bitrix_tasks_list` сортирует `{"ID":"desc"}`), у `bitrix_call` их нет —
25
+ один и тот же фильтр даст разные страницы. Сравниваешь выдачи — задавай `order` явно.
26
+ - Ответ `invalid_csrf` / `invalid_authentication` = сессия портала протухла → перелогинься в браузере
27
+ на вкладке портала.
38
28
 
39
29
  ---
40
30
 
41
- ## 2. Формат ответа (важно!)
31
+ ## 2. Формат ответа
42
32
 
43
33
  Ajax-контроллеры (`/bitrix/services/main/ajax.php`) отвечают HTTP 200 с конвертом:
44
34
 
@@ -48,272 +38,297 @@ Ajax-контроллеры (`/bitrix/services/main/ajax.php`) отвечают
48
38
  "errors": [] } // ПУСТОЙ массив при успехе! (в JS [] — truthy)
49
39
  ```
50
40
 
51
- - Успех определяется `status:"success"` и/или **пустым** `errors`. Непустой `errors` / `status:"error"` /
52
- top-level `error` — реальная ошибка (мост маппит это в `ok:false`).
53
- - REST-эндпоинты (`/rest/*.json`) отвечают иначе: `{ "result": …, "next", "total", "time" }` — без `errors`.
41
+ Успех = `status:"success"` и/или **пустой** `errors`. Непустой `errors` / `status:"error"` / top-level
42
+ `error` — реальная ошибка (мост маппит её в `ok:false`). REST-эндпоинты (`/rest/*.json`) отвечают
43
+ иначе: `{ "result": …, "next", "total", "time" }` — без `errors`.
54
44
 
55
45
  ---
56
46
 
57
- ## 3. Транспорт: form vs json (различается по методу!)
47
+ ## 3. Каталог: имя → action → params
58
48
 
59
- У каждой записи каталога есть **`bodyType`** — как мост кодирует тело:
49
+ Форма `params` различается по методу: где-то плоско, где-то вложенно, где-то обёрнуто в `params[…]` —
50
+ колонка «params» показывает точную форму (кодирование тела мост берёт на себя).
60
51
 
61
- - **`bodyType: "form"`** (по умолчанию) — `application/x-www-form-urlencoded`; вложенность кодируется
62
- PHP-стилем (`filter[STATUS]=2`, `select[0]=ID`). Объект в `params` рекурсивно разворачивается в такие
63
- ключи. Классические `tasks.task.*`, мессенджер `im.v2.*`, REST — всё это форма.
64
- - **`bodyType: "json"`** — тело уходит как `JSON.stringify(params)` с `Content-Type: application/json`.
65
- Так работают **`tasks.v2.*`** (напр. `tasks.v2.Task.get` ждёт `{"task":{"id":N}}`) и `ui.entityselector.*`.
66
- `params` при этом сохраняет вложенную структуру как есть (не разворачивается в bracket-ключи).
67
-
68
- > Историческая заметка: раньше мост умел только форму, и `tasks.v2.*` были непригодны. Теперь
69
- > поддержаны — в записи каталога ставь `"bodyType": "json"`, и мост отправит их корректно.
70
-
71
- **CSRF / sessid:** мост всегда шлёт заголовок **`X-Bitrix-Csrf-Token: <sessid>`** (свежий
72
- `BX.bitrix_sessid()`). Form-тела дополнительно несут `sessid` полем в теле (legacy REST это требует);
73
- JSON-тела несут sessid **только** в заголовке. Ответ `invalid_csrf`/`invalid_authentication` = сессия
74
- протухла (перелогинься в браузере).
75
-
76
- | Метод (action / endpoint) | bodyType | Где `select`/`filter`/`order` | Пагинация |
52
+ | Имя (`bitrix_call`) | action / endpoint | params | Пагинация |
77
53
  |---|---|---|---|
78
- | `tasks.task.list` | form | **верхний уровень**: `select[]`, `filter{}`, `order{}` | `start` (сдвиг, шаг 50) |
79
- | `tasks.task.get` | form | верхний уровень: `taskId`, `select[]` | — |
80
- | `tasks.v2.Task.get` | **json** | `{"task": {"id": N}}` | — |
81
- | `tasks.v2.Scrum.getTaskInfo` | **json** | `{"taskId": N}` | — |
82
- | `tasks.v2.File.listObjects` | **json** | `{"ids": [N,…]}` | — |
83
- | `tasks.v2.Task.View.User.count` | **json** | `{"task": {"id": N}}` | — |
84
- | `tasks.v2.Task.Relation.Child.list` | **json** | `{"taskId": N, "withIds":true, "navigation":{"size":N}}` | `navigation` |
85
- | `tasks.v2.Task.Relation.Related.list` | **json** | `{"taskId": N, …}` (как Child) | `navigation` |
86
- | `socialnetwork.api.workgroup.list` | form | **верхний уровень**: `select[]`, `order{}`, `filter{}` | (nav — не проверено) |
87
- | `socialnetwork.api.workgroup.get` | form | **обёрнуто**: `params[groupId]`, `params[select][]` | — |
88
- | `/rest/im.recent.list.json` | form (rest) | плоско: `LIMIT`, `SKIP_OPENLINES`, `UNREAD_ONLY`, … | `LIMIT` |
89
- | `/rest/im.user.get.json` | form (rest) | плоско: `ID` | — |
90
- | `im.v2.Recent.load` | form | плоско: `limit`, `filter[recentSection]`, `filter[unread]`, `filter[parentId]` | `im.v2.Recent.tail` |
91
- | `im.v2.Recent.tail` | form | плоско: `limit`, `filter[lastMessageDate]` (курсор), `filter[recentSection]` | `filter[lastMessageDate]` |
92
- | `im.v2.Chat.load` | form | плоско: `dialogId`\|`chatId`, `messageLimit` | — |
93
- | `im.v2.Chat.getDialogId` | form | плоско: `externalId` | — |
94
- | `im.v2.Chat.Message.list` | form | плоско: `chatId`, `limit` | только последняя страница — назад **не листает**, используй `.tail` |
95
- | `im.v2.Chat.Message.tail` | form | плоско: `chatId`, `limit`, `filter[lastId]`, `order[id]` | **см. раздел 6** |
96
- | `im.v2.Chat.Message.read` | form | плоско: `chatId`, `ids[]`, `actionUuid` | — (мутирующий) |
97
- | `im.v2.Chat.readAll` | form | без параметров | — (мутирующий) |
98
- | `ui.entityselector.load` | **json** | `{"dialog": {entities,preselectedItems,…}}` | — |
99
- | `ui.entityselector.doSearch` | **json** | `{"dialog": {…}, "searchQuery": {"query": "...", "queryWords": ["..."]}}` | — |
54
+ | `tasks.list` | `tasks.task.list` | верхний уровень: `filter{RESPONSIBLE_ID,REAL_STATUS,GROUP_ID,…}`, `select[]`, `order{}` | **`PAGEN_1`** — номер страницы (1, 2, 3…), страница = 20 задач; `start` **не работает** (вернёт ту же первую страницу) |
55
+ | `task.get` | `tasks.task.get` | верхний уровень: `taskId`, `select[]` | — |
56
+ | `task.v2.get` | `tasks.v2.Task.get` | вложенно: `{"task":{"id":N}}` | — |
57
+ | `task.scrum.info` | `tasks.v2.Scrum.getTaskInfo` | `{"taskId":N}` | — |
58
+ | `task.files` | `tasks.v2.File.listObjects` | `{"ids":[N,…]}` | — |
59
+ | `task.views.count` | `tasks.v2.Task.View.User.count` | вложенно: `{"task":{"id":N}}` | — |
60
+ | `task.subtasks` | `tasks.v2.Task.Relation.Child.list` | `{"taskId":N,"withIds":true,"navigation":{"size":N}}` | `navigation` |
61
+ | `task.related` | `tasks.v2.Task.Relation.Related.list` | как `task.subtasks` | `navigation` |
62
+ | `projects.list` | `socialnetwork.api.workgroup.list` | верхний уровень: `select[]`, `order{}`, `filter{}` | `start` (шаг 50; `start=(N-1)*50`, `-1` — ответ без `total`) 📖 |
63
+ | `projects.get` | `socialnetwork.api.workgroup.get` | **обёрнуто**: `params[groupId]`, `params[select][]` | — |
64
+ | `chats.recent` | `/rest/im.recent.list.json` | плоско: `LIMIT`, `SKIP_OPENLINES`, `UNREAD_ONLY` | `LIMIT` |
65
+ | `recent.load` | `im.v2.Recent.load` | плоско: `limit`, `filter[recentSection]` (`tasksTask`/`collab`/…), `filter[unread]`, `filter[parentId]` | `recent.tail` |
66
+ | `recent.tail` | `im.v2.Recent.tail` | плоско: `limit`, `filter[lastMessageDate]` (курсор), `filter[recentSection]` | `filter[lastMessageDate]` |
67
+ | `chat.load` | `im.v2.Chat.load` | плоско: `dialogId`\|`chatId`, `messageLimit` | — |
68
+ | `chat.dialogId` | `im.v2.Chat.getDialogId` | плоско: `externalId` (напр. `"sg"+groupId`) | — |
69
+ | `chat.messages` | `im.v2.Chat.Message.list` | плоско: `chatId`, `limit` | только последняя страница — назад **не листает**, используй `chat.messages.tail` |
70
+ | `chat.messages.tail` | `im.v2.Chat.Message.tail` | плоско: `chatId`, `limit`, `filter[lastId]`, `order[id]` | `filter[lastId]` (§5.1) |
71
+ | `chat.message.read` | `im.v2.Chat.Message.read` | плоско: `chatId`, `ids[]`, `actionUuid` | ⚠ мутирующий |
72
+ | `chat.read.all` | `im.v2.Chat.readAll` | без параметров | ⚠ мутирующий |
73
+ | `im.user.get` | `/rest/im.user.get.json` | плоско: `ID` | — |
74
+ | `im.chat.get` | `/rest/im.chat.get.json` | плоско: `ENTITY_TYPE`, `ENTITY_ID` (напр. `TASKS_TASK`+taskId) | — |
75
+ | `entityselector.load` | `ui.entityselector.load` | `{"dialog":{entities,preselectedItems,…}}` | — |
76
+ | `entityselector.search` | `ui.entityselector.doSearch` | `{"dialog":{…},"searchQuery":{"query":"…","queryWords":["…"]}}` | — |
77
+ | `call.detail` | `GET /call/detail/{callId}` | `callId` (подставляется в путь), `IFRAME=Y`, `IFRAME_TYPE=SIDE_SLIDER` → HTML | см. §5.4 |
100
78
 
101
79
  ---
102
80
 
103
- ## 4. Каталог: имя → action → параметры
104
-
105
- | Имя (`actions.json`) | action / endpoint | bodyType | Ключевые params |
106
- |---|---|---|---|
107
- | `tasks.list` | `tasks.task.list` | form | `filter{RESPONSIBLE_ID,REAL_STATUS,GROUP_ID,…}`, `select[]`, `order{}`, `start` |
108
- | `task.get` | `tasks.task.get` | form | `taskId`, `select[]` |
109
- | `task.v2.get` | `tasks.v2.Task.get` | json | `task.id` (id задачи) |
110
- | `task.scrum.info` | `tasks.v2.Scrum.getTaskInfo` | json | `taskId` |
111
- | `task.files` | `tasks.v2.File.listObjects` | json | `ids` (массив id) |
112
- | `task.views.count` | `tasks.v2.Task.View.User.count` | json | `task.id` (id задачи) |
113
- | `task.subtasks` | `tasks.v2.Task.Relation.Child.list` | json | `taskId`, `navigation{size}` |
114
- | `task.related` | `tasks.v2.Task.Relation.Related.list` | json | `taskId`, `navigation{size}` |
115
- | `projects.list` | `socialnetwork.api.workgroup.list` | form | `select[]`, `order{}`, `filter{}` |
116
- | `projects.get` | `socialnetwork.api.workgroup.get` | form | `params[groupId]`, `params[select][]` |
117
- | `chats.recent` | `/rest/im.recent.list.json` | form | `LIMIT`, `UNREAD_ONLY`, `SKIP_OPENLINES` |
118
- | `recent.load` | `im.v2.Recent.load` | form | `filter[recentSection]` (`tasksTask`/`collab`/…), `limit`, `filter[unread]` |
119
- | `recent.tail` | `im.v2.Recent.tail` | form | `filter[lastMessageDate]`, `filter[recentSection]`, `limit` |
120
- | `chat.load` | `im.v2.Chat.load` | form | `dialogId`\|`chatId`, `messageLimit` |
121
- | `chat.dialogId` | `im.v2.Chat.getDialogId` | form | `externalId` (напр. `"sg"+groupId`) |
122
- | `chat.messages` | `im.v2.Chat.Message.list` | form | `chatId`, `limit` (последние; вглубь — `chat.messages.tail`) |
123
- | `chat.messages.tail` | `im.v2.Chat.Message.tail` | form | `chatId`, `filter[lastId]`, `order[id]`, `limit` |
124
- | `chat.message.read` | `im.v2.Chat.Message.read` | form | `chatId`, `ids[0]`, `actionUuid` |
125
- | `chat.read.all` | `im.v2.Chat.readAll` | form | — (⚠ мутирующий) |
126
- | `im.user.get` | `/rest/im.user.get.json` | form | `ID` |
127
- | `entityselector.load` | `ui.entityselector.load` | json | `dialog` (объект) |
128
- | `entityselector.search` | `ui.entityselector.doSearch` | json | `dialog`, `searchQuery{query,queryWords}` |
129
-
130
- ---
131
-
132
- ## 5. Справочник полей
133
-
134
- **Задача** (`tasks.task.*`): `ID`, `TITLE`, `DESCRIPTION`, `STATUS`, `REAL_STATUS`, `RESPONSIBLE_ID`,
135
- `CREATED_BY`, `CREATED_DATE`, `CHANGED_DATE`, `DEADLINE`, `CLOSED_DATE`, `PRIORITY`, `GROUP_ID`,
136
- `TAGS`, `TIME_ESTIMATE`, `UF_*`. Ответ также подкладывает объекты `group`, `responsible`, `creator`, `action`.
137
-
138
- Статусы задачи (`STATUS`): `1` Новая · `2` Ждёт выполнения · `3` Выполняется · `4` Ждёт контроля ·
139
- `5` Завершена · `6` Отложена · `7` Отклонена.
140
-
141
- **Задача v2** (`tasks.v2.*`): id задачи передаётся вложенно — **`{"task": {"id": N}}`** (у `Task.get` и
142
- `Task.View.User.count`), а у `Scrum.getTaskInfo` и `Relation.*` — плоско `{"taskId": N}`. Это отдельная
143
- подсистема scrum-борда; структуру ответа сверь реверсом — в захвате видны только параметры запроса.
144
-
145
- **Группа/проект — список** (`workgroup.list`, camelCase в ответе): `ID`, `NAME`, `DESCRIPTION`,
146
- `NUMBER_OF_MEMBERS`, `OWNER_ID`, `DATE_CREATE`, `PROJECT` (Y/N), `TYPE`.
81
+ ## 4. Справочник полей
82
+
83
+ **Задача** (`tasks.task.*`) — поля для `select`/`filter` в UPPER_CASE: `ID`, `TITLE`, `DESCRIPTION`,
84
+ `STATUS`, `REAL_STATUS`, `RESPONSIBLE_ID`, `CREATED_BY`, `CREATED_DATE`, `CHANGED_DATE`, `DEADLINE`,
85
+ `CLOSED_DATE`, `PRIORITY`, `GROUP_ID`, `TAGS`, `TIME_ESTIMATE`, **`CHAT_ID`** (id чата-обсуждения
86
+ задачи — прямой резолвер `taskId → chatId`, §5.2), `UF_*`. Ответ подкладывает объекты `group`,
87
+ `responsible`, `creator`, `action`.
88
+ 📖 (`tasks.task.get`/`list`) — сверх этого: `parentId`, `stageId`, `sprintId`, `backlogId`,
89
+ `commentsCount`, `timeSpentInLogs`, `favorite`, `flowId`, `mark`, `accomplices[]`, `auditors[]`,
90
+ `checklist{}`, `subStatus`; в официальном REST-ответе поля приходят **camelCase**, а в `select`/`filter`
91
+ они всегда **UPPER_CASE**. Доп. вычисления — флагами `params{WITH_TIMER_INFO, WITH_RESULT_INFO,
92
+ WITH_PARSED_DESCRIPTION}`.
93
+
94
+ **Статусы** (`REAL_STATUS` — «настоящее» числовое состояние): `1` Новая · `2` Ждёт выполнения ·
95
+ `3` Выполняется · `4` Ждёт контроля · `5` Завершена · `6` Отложена · `7` Отклонена. Поле `STATUS`
96
+ поверх этого несёт **мета-состояния** отображения (почти просрочена / не просмотрена / просрочена),
97
+ поэтому «работает ли задача сейчас» фильтруй по **`REAL_STATUS`** (напр. `{"REAL_STATUS":3}`) 📖.
98
+ Асимметрия: в **`filter`** работает `REAL_STATUS`, а в **`select`** его указывать бесполезно — поле молча
99
+ не вернётся; чтобы получить состояние, проси `select:["STATUS"]` → в ответе будет ключ `status` со
100
+ значением по той же шкале 1..7 (у обёртки `bitrix_tasks_list` он уже в дефолтном `select`).
101
+
102
+ **Фильтр `tasks.task.list`** — оператор ставится перед именем поля: `!` (не равно / исключить),
103
+ `<`, `<=`, `>`, `>=`, `%` (LIKE-подстрока). Напр. `{"!REAL_STATUS":5}` — все **незакрытые**,
104
+ `{">=DEADLINE":"2026-07-01"}`, `{"%TITLE":"карта"}`. Фильтруемые поля: `ID`, `PARENT_ID`, `GROUP_ID`,
105
+ `CREATED_BY`, `RESPONSIBLE_ID`, `ACCOMPLICE`, `AUDITOR`, `REAL_STATUS`, `STATUS`, `PRIORITY`, `TAG`,
106
+ `STAGE_ID`, `SPRINT_ID`, `BACKLOG_ID`, `DEADLINE`, `*_DATE`, `UF_CRM_TASK`.
107
+
108
+ > **«Мои задачи» ≠ только `RESPONSIBLE_ID`.** Роли участника раздельны: ответственный
109
+ > (`RESPONSIBLE_ID`), соисполнитель (`ACCOMPLICE`), наблюдатель (`AUDITOR`), постановщик
110
+ > (`CREATED_BY`); единого «любая роль» поля в `list` нет — чтобы собрать **все** свои задачи, объедини
111
+ > результаты нескольких вызовов (минимум `RESPONSIBLE_ID` + `ACCOMPLICE`) и **дедуплицируй по `id`** —
112
+ > одна задача часто попадает сразу в несколько ролей. В **`filter`** роли в **единственном** числе
113
+ > (`ACCOMPLICE`/`AUDITOR`), в **`select`** — во **множественном** (`ACCOMPLICES`/`AUDITORS`).
114
+
115
+ > **Scrum-задачи (спринты).** Поле `SPRINT_ID` задачи и жёсткий `filter[SPRINT_ID]` могут расходиться:
116
+ > задача числится за спринтом по своему полю, но не попадает в выборку по фильтру (возвращена в бэклог
117
+ > или в иной stage). Если по фильтру «маловато» — перепроверь без него и отфильтруй по
118
+ > `SPRINT_ID`/`STAGE_ID` у себя. Метода «колонки scrum-доски» (`STAGE_ID` → имя колонки) в каталоге нет.
119
+
120
+ **Группа/проект — список** (`workgroup.list`): `ID`, `NAME`, `DESCRIPTION`, `NUMBER_OF_MEMBERS`,
121
+ `OWNER_ID`, `DATE_CREATE`, `PROJECT` (Y=проект/N=группа), `TYPE`; **без `select` вернётся только `ID`**.
122
+ 📖 фильтруемые: `ID`, `NAME`, `OWNER_ID`, `ACTIVE`, `VISIBLE`, `OPENED`, `CLOSED`, `PROJECT`,
123
+ `SUBJECT_ID`, `SITE_ID`, `DATE_CREATE`, `DATE_UPDATE`, `DATE_ACTIVITY`; операторы фильтра
124
+ `>= > <= < % =% %= !% != !`.
147
125
  **Группа — карточка** (`workgroup.get`): плюс `OWNER_DATA`, `SUBJECT_DATA`, `MEMBERS[]`,
148
- `MODERATOR_MEMBERS[]`, `CHAT_ID`, `DIALOG_ID`, `IMAGE_ID`, UF-поля.
126
+ `MODERATOR_MEMBERS[]`, **`CHAT_ID`**, `DIALOG_ID`, `IMAGE_ID`, UF-поля.
127
+
128
+ **Недавний чат** (`im.recent.list`): `id` (это dialogId: число = юзер, `"chat"+N` = чат), `chat_id`,
129
+ `type` (`user`/`chat`), `title`, `message{id,text,file,author_id,date,status}`, `counter` (непрочитанных),
130
+ `unread`, `last_id`, `pinned`, `date_update`, `date_last_activity`, `user{…}`,
131
+ `chat{… entity_type, entity_id, owner, …}`; есть ли ещё страницы — `hasMore`/`hasMorePages` 📖.
132
+ **Недавние по секции** (`im.v2.Recent.load`): `filter[recentSection]` — `default` (обычные диалоги),
133
+ **`tasksTask` (чаты задач)**, `collab`/`collabDefault`. Вглубь — `im.v2.Recent.tail` с курсором
134
+ `filter[lastMessageDate]` (ISO-дата последнего элемента страницы).
149
135
 
150
- **Недавний чат** (`im.recent.list`): `chat_id`, `title`, `type`, `message{id,text,date,author_id}`,
151
- `last_id`, `unread`, `pinned`, `user{…}`, `chat{…}`.
152
- **Недавние по секции** (`im.v2.Recent.load`): фильтр `filter[recentSection]` — `default` (обычные
153
- диалоги), **`tasksTask` (чаты задач)**, `collab`/`collabDefault` (коллабы). Листание вглубь —
154
- `im.v2.Recent.tail` с курсором `filter[lastMessageDate]` (ISO-дата последнего элемента страницы).
155
136
  **Карточка юзера** (`im.user.get`): `id`, `name`, `first_name`, `last_name`, `avatar`, `work_position`,
156
137
  `gender`, `status`, `online` — резолв автора сообщения (`authorId`) в имя.
157
- **Сообщение** (`im.v2.Chat.Message.*`): `id`, `chatId`, `authorId`, `date`, `text`, `params`, `viewed`.
158
- Ответ также несёт `users[]` (участники), `additionalMessages[]`, `hasPrevPage`/`hasNextPage`.
159
- `chatId` берётся из `im.recent.list` (`chat_id`) или из `workgroup.get` (`CHAT_ID`/`DIALOG_ID`).
160
-
161
- **Адресация чата (`chat.load`): `dialogId` vs `chatId`.** `chat.load` принимает любой из двух:
162
- - `dialogId` = **ID пользователя** (число, напр. `11`) → **личный чат 1-на-1** с этим пользователем.
163
- - `dialogId` = **`"chat"+CHAT_ID`** (напр. `"chat7111"`) → **групповой чат** (проекта/задачи/канала).
164
- - `chatId` = числовой `CHAT_ID` — то же, что групповой `dialogId`, но без префикса.
165
138
 
166
- Ответ `chat.load` возвращает числовой `chatId` — используй его дальше для `chat.messages` /
167
- `chat.messages.tail` (листание вглубь) и `chat.message.read` (эти три работают по `chatId`, не по `dialogId`).
139
+ **Сообщение** (`im.v2.Chat.Message.*`): `id`, `chatId`, `authorId`, `date`, `text`, `params`, `viewed`;
140
+ ответ также несёт `users[]` (участники), `additionalMessages[]`, `hasPrevPage`/`hasNextPage`.
168
141
 
169
142
  ---
170
143
 
171
- ## 6. Примеры вызовов
144
+ ## 5. Сценарии
172
145
 
173
146
  ```jsonc
174
147
  // открытые задачи ответственного 55, по дедлайну, вторая страница
175
148
  bitrix_tasks_list { "params": { "filter": {"RESPONSIBLE_ID":55,"REAL_STATUS":2},
176
149
  "order": {"DEADLINE":"asc"}, "start": 50 } }
177
150
 
178
- // карточка задачи с расширенным набором полей
179
- bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","TITLE","DESCRIPTION","TAGS","TIME_ESTIMATE"] } }
180
-
181
- // карточка задачи через v2-подсистему (JSON-тело; id вложен в task)
151
+ // карточка задачи с расширенным набором полей; v2-подсистема; файлы, просмотры, связи
152
+ bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","TITLE","DESCRIPTION","TAGS"] } }
182
153
  bitrix_task_get_v2 { "taskId": 4229 } // → { "task": { "id": 4229 } }
183
- bitrix_call { "name": "task.v2.get", "params": { "task": { "id": 4229 } } }
184
-
185
- // файлы задачи и счётчик просмотров (v2, JSON-тело); подзадачи/связанные
186
154
  bitrix_call { "name": "task.files", "params": { "ids": [4229] } }
187
- bitrix_call { "name": "task.views.count", "params": { "task": { "id": 4229 } } }
188
155
  bitrix_task_subtasks { "taskId": 4229 }
189
156
  bitrix_task_related { "taskId": 4229 }
190
157
 
191
- // проекты по дате создания; полная карточка группы
158
+ // проекты по дате создания; полная карточка группы (отдаёт CHAT_ID / DIALOG_ID)
192
159
  bitrix_projects_list { "params": { "order": {"DATE_CREATE":"desc"} } }
193
- bitrix_project_get { "groupId": 15 }
160
+ bitrix_project_get { "groupId": 15 }
161
+ ```
194
162
 
195
- // открыть чат (первые сообщения)
196
- bitrix_chat_load { "chatId": 485, "messageLimit": 50 }
163
+ ### 5.1. Мессенджер (IM): как получить `chatId`
197
164
 
198
- // ЛИСТАНИЕ ИСТОРИИ ВГЛУБЬ (старые сообщения): beforeId = минимальный id сообщения
199
- // из текущей страницы. Повторяй, уменьшая beforeId, до начала истории (order DESC — дефолт).
200
- bitrix_chat_history { "chatId": 485, "beforeId": 1861279 }
165
+ Всё общение — **чаты**: личные 1-на-1, групповые (проектов, каналов) и **чаты-обсуждения задач**.
166
+ Числовой **`chatId`** — ключ ко всем операциям с сообщениями (`bitrix_chat_messages`,
167
+ `bitrix_chat_history`, `bitrix_chat_mark_read`); почти любая ошибка агента здесь — на шаге его получения.
201
168
 
202
- // пометить сообщения прочитанными (МУТИРУЮЩИЙ). actionUuid генерируется автоматически.
203
- bitrix_chat_mark_read { "chatId": 40271, "ids": [1884131] }
204
- ```
169
+ **Три идентификатора одного чата** (годятся для разных операций):
170
+
171
+ | Идентификатор | Что это | Пример | Куда передавать |
172
+ |---|---|---|---|
173
+ | `chatId` | числовой id чата | `485` | сообщения / история / отметка прочитанным |
174
+ | `dialogId` | адрес для ОТКРЫТИЯ: `userId` (личный 1-на-1) **или** `"chat"+chatId` (групповой) | `11`, `"chat485"` | `bitrix_chat_load` |
175
+ | `externalId` | внешний ключ сущности → резолвится в `dialogId` | `"sg15"` (соцгруппа 15) | `bitrix_chat_get_dialog_id` |
176
+
177
+ Ключевое: **личный чат с человеком = его `userId` в роли `dialogId`** (отдельного «id чата» у 1-на-1
178
+ знать не нужно). `bitrix_chat_load` принимает `dialogId` **или** `chatId` и всегда **возвращает
179
+ числовой `chatId`** — дальше оперируешь им.
205
180
 
206
- ### 6.1. Сценарий: чат с конкретным пользователем
181
+ **Как получить `chatId` — по цели:**
207
182
 
208
- Личный чат 1-на-1 адресуется `dialogId` = **ID пользователя**. Id берётся из задачи
209
- (`RESPONSIBLE_ID`/`CREATED_BY`), из `bitrix_chats_recent`, либо резолвится поиском (§6.3).
183
+ | Хочу открыть чат… | Шаги |
184
+ |---|---|
185
+ | **с пользователем по ИМЕНИ** | `bitrix_entity_search { query:"дмитрий" }` → диалог с `dialogId` = его userId → `bitrix_chat_load { dialogId }` |
186
+ | **с пользователем, id известен** | `bitrix_chat_load { dialogId: <userId> }` (userId — из задачи `RESPONSIBLE_ID`/`CREATED_BY`, из `bitrix_chats_recent`, из `bitrix_user_get`) |
187
+ | **обсуждение ЗАДАЧИ** | `bitrix_task_get` → `CHAT_ID` (§5.2) |
188
+ | **ПРОЕКТА/группы** | `bitrix_task_get`→`GROUP_ID`→`bitrix_project_get`→`CHAT_ID`; либо `bitrix_chat_get_dialog_id { externalId:"sg<groupId>" }` |
189
+ | **не знаю точно, какой** | `bitrix_chats_recent` (недавние, сопоставь по `title`/`user`) или `bitrix_entity_search { query }` |
190
+
191
+ > Почему не «просто список чатов»: `bitrix_chats_recent` (`im.recent.list`) отдаёт лишь недавние и
192
+ > **без чатов задач**. Для поиска конкретного чата — `bitrix_entity_search`; для чатов задач —
193
+ > `bitrix_recent_load { section:"tasksTask" }`.
194
+
195
+ **Читать историю.** `bitrix_chat_messages` отдаёт только последнюю страницу; вглубь листает
196
+ `bitrix_chat_history` по курсору `beforeId` = минимальный id сообщения текущей страницы (повторяй,
197
+ уменьшая `beforeId`, до начала истории).
210
198
 
211
199
  ```jsonc
212
- // 1) открыть личный чат с пользователем 11 → в ответе будет числовой chatId
213
- bitrix_chat_load { "dialogId": 11 }
214
- // 2) листать его историю вглубь по chatId из ответа шага 1
200
+ bitrix_chat_load { "dialogId": 11 } // личный чат юзера 11 → в ответе chatId
215
201
  bitrix_chat_history { "chatId": 485, "beforeId": 1861279 }
202
+ bitrix_chat_mark_read { "chatId": 40271, "ids": [1884131] } // ⚠ мутирующий; actionUuid — автоматом
216
203
  ```
217
204
 
218
- ### 6.2. Сценарий: обсуждение (чат) конкретной задачи
205
+ 📖 Публичные аналоги чтения сообщений: `im.dialog.messages.get` (дефолт 20, макс 50; листание
206
+ `LAST_ID` — старее, `FIRST_ID` — новее); `imbot.v2 chat.getMessageContext` — ответ `messages[]`
207
+ (oldest→newest) + `users[]` + `hasPrevPage`/`hasNextPage`, по форме **совпадает** с внутренними
208
+ `im.v2.Chat.Message.*`.
219
209
 
220
- У каждой задачи есть свой im-чат-обсуждение. Два пути найти его `chatId`:
210
+ ### 5.2. Обсуждение (чат) конкретной задачи
221
211
 
222
- **А. Через список чатов задач (прямой).** `im.v2.Recent.load` с секцией `tasksTask` отдаёт чаты задач
223
- (в `bitrix_chats_recent` / `/rest/im.recent.list` их НЕТ). Листать вглубь — `bitrix_recent_tail`.
212
+ **А. Прямой резолв — предпочтительно.** `bitrix_task_get` возвращает `CHAT_ID` чата-обсуждения (входит
213
+ в дефолтный `select` 📖) — это `taskId → chatId` без поиска по названию и без промаха по тёзкам:
224
214
 
225
215
  ```jsonc
226
- bitrix_recent_load { "section": "tasksTask" } // → чаты задач с chatId/dialogId
227
- bitrix_recent_tail { "section": "tasksTask", "lastMessageDate": "2026-06-29T17:25:38+03:00" }
228
- // открыть найденный чат и листать историю
229
- bitrix_chat_load { "dialogId": "chat38849" } // или { "chatId": 38849 }
230
- bitrix_chat_history { "chatId": 38849, "beforeId": 1722353 }
216
+ bitrix_task_get { "taskId": 28373 } // в ответе CHAT_ID
217
+ bitrix_chat_load { "chatId": <CHAT_ID> }
218
+ bitrix_chat_history { "chatId": <CHAT_ID>, "beforeId": <мин id страницы> }
231
219
  ```
232
220
 
233
- **Б. Поиском по названию задачи** — `bitrix_entity_search` с секцией `tasksTask`:
221
+ Общая альтернатива — **`bitrix_entity_chat`** (обёртка над `im.chat.get`): chatId любого связанного
222
+ объекта по паре `entityType`/`entityId`; кроме `TASKS_TASK` так же адресуются `SONET_GROUP`
223
+ (группа/проект), `CRM`, `CALENDAR`, `MAIL`, `VIDEOCONF`, `CALL`:
234
224
 
235
225
  ```jsonc
236
- bitrix_entity_search { "query": "трекер", "section": "tasksTask" }
226
+ bitrix_entity_chat { "entityType": "TASKS_TASK", "entityId": 28373 } // → chatId
237
227
  ```
238
228
 
229
+ **Б. Через список чатов задач** (секция `tasksTask`; в `bitrix_chats_recent` их НЕТ):
230
+
231
+ ```jsonc
232
+ bitrix_recent_load { "section": "tasksTask" } // → чаты задач с chatId/dialogId
233
+ bitrix_recent_tail { "section": "tasksTask", "lastMessageDate": "2026-06-29T17:25:38+03:00" }
234
+ bitrix_chat_load { "dialogId": "chat38849" } // или { "chatId": 38849 }
235
+ ```
236
+
237
+ **В. Поиском по названию задачи** (запасной, матч по тексту):
238
+ `bitrix_entity_search { "query": "трекер", "section": "tasksTask" }`
239
+
239
240
  **Чат ПРОЕКТА задачи** (обсуждение группы, не самой задачи) — через `GROUP_ID`:
240
241
 
241
242
  ```jsonc
242
243
  bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","GROUP_ID"] } }
243
244
  bitrix_project_get { "groupId": 15 } // отдаёт CHAT_ID / DIALOG_ID
244
- bitrix_chat_load { "dialogId": "chat7111" }
245
245
  bitrix_chat_get_dialog_id { "externalId": "sg15" } // либо резолв dialogId соцгруппы напрямую
246
246
  ```
247
247
 
248
- ### 6.3. Сценарий: поиск чата / пользователя
248
+ ### 5.3. Поиск чата / пользователя
249
249
 
250
- Текстовый поиск — `bitrix_entity_search` (обёртка над `ui.entityselector.doSearch`): сам собирает
251
- диалог `IM_CHAT_SEARCH` и `searchQuery` из строки:
250
+ `bitrix_entity_search` (обёртка над внутренним `ui.entityselector.doSearch`) сам собирает диалог
251
+ `IM_CHAT_SEARCH` и `searchQuery` из строки. Публичные аналоги: люди — `user.get`/`user.search`, чаты —
252
+ `im.recent.list` + фильтрация у себя.
252
253
 
253
254
  ```jsonc
254
255
  bitrix_entity_search { "query": "дмитрий" } // среди всех чатов/диалогов
255
256
  bitrix_entity_search { "query": "трекер", "section": "tasksTask" } // среди чатов задач
257
+ bitrix_user_get { "userId": 11 } // резолв authorId → имя
256
258
  ```
257
259
 
258
- Быстрый путь без поиска — недавние чаты и фильтрация на стороне агента; карточка автора по id:
260
+ ### 5.4. Созвон: резюме, расшифровка, запись
261
+
262
+ AI-анализ звонка (BitrixGPT Follow-Up) живёт на серверно отрендеренной странице `/call/detail/<callId>`
263
+ — JSON-эндпоинта под ней нет, публичного REST для видеовстреч тоже (📖 только телефония `voximplant.*`
264
+ и `crm.activity.call.getTranscript`). Мост читает эту страницу и разбирает её в нормализованный JSON.
265
+
266
+ **1) Найти звонки.** Серверного поиска звонков по порталу нет — ищем в конкретном чате: звонок
267
+ оставляет системное сообщение с `params.COMPONENT_PARAMS = { messageType: "START", callId }`.
259
268
 
260
269
  ```jsonc
261
- bitrix_chats_recent { } // сопоставь по title / user
262
- bitrix_recent_load { "section": "tasksTask" }
263
- bitrix_user_get { "userId": 11 } // резолв authorId → имя
270
+ bitrix_chat_calls { "chatId": 9876 } // чат проекта: bitrix_project_get → CHAT_ID
271
+ bitrix_chat_calls { "chatId": 9876, "maxPages": 20, "beforeId": 5001 } // продолжить вглубь
264
272
  ```
265
273
 
266
- ### 6.4. Легаси-комментарии задачи (HTML-поддомен — пока НЕ в мосте)
267
-
268
- У задач два независимых фида обсуждения:
269
- - **im.v2-чат** — структурный JSON, читается уже сейчас (§6.2); обычно этого достаточно.
270
- - **Форумные легаси-комментарии** — рендерятся как **HTML** в side-slider'е. Это отдельный
271
- «HTML-поддомен» внутреннего API, который **текущий мост не поддерживает** (блокеры ниже).
274
+ Ответ: `calls[] { callId, startedAt, startedBy, startMessageId, events[], summaryMessageId }` плюс
275
+ `scannedPages` / `oldestScannedMessageId` / **`reachedHistoryStart`** — если он `false`, история
276
+ дочитана НЕ до конца, продолжай с `beforeId: <oldestScannedMessageId>`.
272
277
 
273
- **Как грузятся (реверс).** Первая страница — GET HTML-страницы iframe (сервер тут же минтит подпись):
278
+ **2) Взять всё по звонку.**
274
279
 
275
- ```
276
- GET /task/comments/<taskId>/?IFRAME=Y&IFRAME_TYPE=SIDE_SLIDER → HTML (комментарии + signedParameters)
280
+ ```jsonc
281
+ bitrix_call_detail { "callId": 4242 } // всё; длинная расшифровка уедет в файл
282
+ bitrix_call_detail { "callId": 4242, "transcript": "inline" } // расшифровка прямо в ответе
283
+ bitrix_call_detail { "callId": 4242, "transcript": "none" } // расшифровка не нужна вовсе
284
+ bitrix_call_detail { "callId": 4242, "transcript": "file" } // всегда в файл, даже короткая
277
285
  ```
278
286
 
279
- Листание — POST компонентного ajax (форма, отдаёт HTML-фрагмент):
287
+ | Поле ответа | Что внутри |
288
+ |---|---|
289
+ | `title`, `agenda`, `meetingType` | тема, вступление, тип встречи («Статус-встреча») |
290
+ | `date`, `interval`, `duration` | «23 июля, 15:46», «15:46 - 16:51», «1 ч 4 мин» |
291
+ | `efficiency`, `qualityChecklist[]` | оценка встречи в % и критерии `{ ok, text }` |
292
+ | `participants[]` | `{ id, name, talkTimePercent, talkTime, efficiency, metrics[], insight }` |
293
+ | `overview`, `chapters[]` | общее резюме и главы `{ from, to, title, text }` с таймкодами |
294
+ | `decisions[]`, `tasks[]` | «что решили» и задачи `{ assigneeId, assignee, text }` |
295
+ | `transcript[]`, `transcriptCount` | реплики `{ from, to, speakerId, speaker, text }` |
296
+ | `recording` | `{ path, url, trackId }` — аудиозапись, качается `bitrix_call_recording` (§5.5) |
297
+
298
+ **Расшифровка длинного звонка не приходит в ответе.** У часовой встречи она даёт ~70 КБ из 80 КБ, поэтому
299
+ свыше ~20 КБ (режим `"auto"` = умолчание) пишется на диск, а в ответе остаются `transcript: []`,
300
+ `transcriptCount` и **`files`**: `files.transcript` — `call-<id>-transcript.txt`, где строка = реплика
301
+ (`[11:10—11:24] Генрих Богацкий: …`), её удобно **грепать** (найденная строка сама несёт таймкод и
302
+ спикера); `files.json` — полный ответ с расшифровкой. Оба файла — кеш звонка, перезаписываются.
303
+
304
+ Тонкости:
305
+ - **`speakerId`** резолвится по имени из таблицы анализа; кто говорил мало и в неё не попал — получит
306
+ `speakerId: null`, но останется в `participants` с `id: null`.
307
+ - **`assigneeId` задачи** — упомянутый в тексте пользователь, а НЕ id смотрящего.
308
+ - Нет доступа к звонку / протухла сессия → инструмент вернёт внятную ошибку (портал отдаёт HTML логина).
309
+
310
+ ### 5.5. Скачать файл (вложение чата, файл задачи, запись звонка)
311
+
312
+ Ссылки портал минтит и подписывает сам, но подпись НЕ заменяет авторизацию — качает расширение в
313
+ сессии пользователя.
280
314
 
315
+ ```jsonc
316
+ // вложение чата: ссылку берём из files[].urlDownload в ответе bitrix_chat_load
317
+ bitrix_file_download { "url": "https://<портал>/bitrix/services/main/ajax.php?action=disk.api.file.download&…" }
318
+ bitrix_call_recording { "callId": 4242 } // аудиозапись созвона (ссылку найдёт сам)
319
+ bitrix_file_download { "url": "…", "savePath": "/tmp/photo.png", "overwrite": true }
281
320
  ```
282
- POST /bitrix/services/main/ajax.php?mode=class&c=bitrix:forum.comments&action=navigateComment
283
- form: AJAX_POST=Y, ENTITY_XML_ID=TASK_<taskId>, taskId=<taskId>, MODE=LIST,
284
- FILTER[<ID]=<курсор: id, ДО которого грузить старые>, PAGEN_1=1,
285
- signedParameters=<подписанный blob>, IFRAME=Y, IFRAME_TYPE=SIDE_SLIDER
286
- headers: bx-ajax: true, x-bitrix-site-id: s1 (плюс обычный X-Bitrix-Csrf-Token)
287
- ```
288
-
289
- `signedParameters` — base64 PHP-массива + HMAC-подпись сервера; кодирует
290
- `FORUM_ID, ENTITY_TYPE=TK, ENTITY_ID=<taskId>, ENTITY_XML_ID=TASK_<taskId>, …`. **Подделать нельзя** —
291
- берётся из HTML первой страницы.
292
-
293
- **Почему пока не в каталоге (3 блокера):**
294
- 1. Мост парсит ответ только как JSON (`extension/src/connector.ts` → `resp.json()`) — HTML бросит исключение.
295
- 2. Path-параметр в URL (`/task/comments/<taskId>/`) — записи каталога статичны, шаблонов пути нет.
296
- 3. `signedParameters` + заголовки `bx-ajax`/`x-bitrix-site-id` — нужен предварительный GET HTML-страницы.
297
321
 
298
- **Чтобы подключить** (будущая работа): научить мост возвращать text/HTML при не-JSON ответе и
299
- поддержать path-шаблон эндпоинта; затем обёртка `bitrix_task_comments { taskId }` (GET первой
300
- страницы), а листание — через `navigateComment` с выпарсенным `signedParameters`.
322
+ Ответ — **путь, а не содержимое**: `{ path, bytes, contentType, fileName }` (гигабайты не попадают в
323
+ контекст). По умолчанию файл ложится в `~/.bitrix24-mcp-bridge/downloads/`; `savePath` без ведущего `/`
324
+ трактуется как имя внутри этой папки. Потолок — 512 МБ.
301
325
 
302
- > Тот же HTML-поддомен: **детали звонка** — `GET /call/detail/<callId>?IFRAME=Y&IFRAME_TYPE=SIDE_SLIDER`
303
- > (отдаёт HTML). Ограничения идентичны.
326
+ Тонкости:
327
+ - **Граница безопасности — origin**: скачивается только с origin сконфигурированного портала.
328
+ - **Имя даёт портал** (`Content-Disposition`), поэтому без `savePath` угадывать его не нужно.
329
+ - **Повторное скачивание** того же файла падает с `already exists` — передай `overwrite: true`.
304
330
 
305
331
  ---
306
332
 
307
- ## 7. Как расширить (новый домен: календарь, диск, CRM, …)
308
-
309
- 1. Сними реальные вызовы (`docs/reconnaissance.md` — авто-запись `bun run capture` или HAR →
310
- `bun run catalog:draft`). Черновик `actions.draft.json` накапливает **все уникальные комбинации
311
- параметров** на экшен и проставляет `bodyType` (form/json) автоматически.
312
- 2. Определи транспорт по разделу 3: если черновик показал `"bodyType": "json"` — перенеси его в запись.
313
- 3. Добавь запись в `actions.json` (может быть мутирующей — каталог это допускает). Проверь через
314
- `bitrix_call { name, params }`.
315
- 4. Если удобно — оберни в типизированный инструмент в `src/tools/register.ts` (с дефолтным `select`).
316
-
317
- > `actions.json` / `actions.draft.json` — данные конкретного портала (gitignored). Этот файл
318
- > (`api-notes.md`) — переносимые знания о самом API; держи его в актуальном состоянии при добавлении
319
- > новых методов.
333
+ Расширение каталога новыми методами, транспорт (`bodyType`, `responseType`, path-параметры) и
334
+ доставка правок в живой daemon — `docs/reconnaissance.md`.