bitrix24-mcp-bridge 0.3.0 → 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,55 +1,34 @@
1
1
  # Bitrix24 API notes (reverse-engineered)
2
2
 
3
- Справочник по API Bitrix24, добытый **живым реверсом**. Часть вызовов моста совпадает с
4
- **публичным REST** (`tasks.task.*`, `im.recent.list`, `im.user.get`, `socialnetwork.api.workgroup.*`) —
5
- по ним есть официальная документация; часть — **сугубо внутренние ajax-контроллеры**
6
- (`im.v2.*`, `tasks.v2.*`, `ui.entityselector.*`), публичных доков по которым нет, только реверс.
7
- Предназначен для любого ИИ-агента или разработчика, который вызывает эти методы через мост
8
- (`bitrix_call` / типизированные инструменты) или расширяет каталог.
9
-
10
- > **Официальные REST-доки** — [`apidocs.bitrix24.com`](https://apidocs.bitrix24.com) (исходники:
11
- > [`github.com/bitrix24/b24restdocs`](https://github.com/bitrix24/b24restdocs); агенту с MCP context7 —
12
- > библиотека `/bitrix24/b24restdocs`). Для публично-документированных методов доки применимы напрямую;
13
- > для внутренних `im.v2.*`/`tasks.v2.*` — как **справочный аналог** (конверт/регистр полей могут
14
- > отличаться, сверяй реверсом). Дальше по тексту такие отсылки помечены **📖**; иди по ним, если нужна
15
- > деталь, которой здесь нет.
16
-
17
- Наблюдения сняты на портале `example.bitrix24.ru` (облако, мессенджер **im.v2**, задачи в режиме
18
- **scrum-борда**). Имена полей и конвенции стандартны для облачного Bitrix24, но при переносе на другой
19
- портал сверяйся реверсом (`docs/reconnaissance.md`).
3
+ Как агенту работать с Bitrix24 через этот мост: формат `params`, имена полей, готовые цепочки вызовов.
4
+
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`).
20
12
 
21
13
  ---
22
14
 
23
15
  ## 1. Как вызывать
24
16
 
25
- - **`bitrix_call { name, params }`** — универсальный вызов любого разрешённого имени из каталога
26
- (`actions.json`). `params` — нативный формат Bitrix (см. раздел 3). Каталог может включать
27
- мутирующие вызовы; allowlist (`actions.json`) — единственная граница, не режим чтения.
28
- - **Типизированные инструменты** — обёртки с разумными дефолтами над каталогом. Точная выборка — через
29
- опциональный `params`, он мержится **последним** и перекрывает дефолты. Регистрируются только те,
30
- чьё имя есть в каталоге (`actions.json`); иначе инструмент пропускается. Текущий набор:
31
- - Задачи: `bitrix_tasks_list`, `bitrix_task_get`, `bitrix_task_get_v2` (v2/JSON),
32
- `bitrix_task_scrum_info`, `bitrix_task_files`, `bitrix_task_views_count`,
33
- `bitrix_task_subtasks` (подзадачи), `bitrix_task_related` (связанные).
34
- - Проекты: `bitrix_projects_list`, `bitrix_project_get`.
35
- - Чаты: `bitrix_chats_recent`, `bitrix_recent_load` (недавние по секции — в т.ч. `tasksTask` =
36
- чаты задач), `bitrix_recent_tail` (листать недавние вглубь), `bitrix_chat_load` (открыть по
37
- `dialogId`/`chatId`), `bitrix_chat_messages`, `bitrix_chat_history` (листать вглубь по `beforeId`),
38
- `bitrix_chat_get_dialog_id` (dialogId по externalId), `bitrix_chat_mark_read` (⚠ мутирующий),
39
- `bitrix_chat_read_all` (⚠ мутирующий).
40
- - Люди/поиск: `bitrix_user_get` (карточка юзера), `bitrix_entity_selector` (загрузка селектора),
41
- `bitrix_entity_search` (текстовый поиск чатов/сущностей через `ui.entityselector.doSearch`),
42
- `bitrix_entity_chat` (chatId чата связанного объекта: задачи/группы/CRM — через `im.chat.get`).
43
-
44
- Любое имя каталога всегда доступно и напрямую через `bitrix_call { name, params }`.
45
- - **`bitrix_help`** — этот же гайд, отдаётся через MCP (инструмент + resource `bitrix://api-notes`).
46
- Источник — **этот файл** (`docs/api-notes.md`).
47
-
48
- Полный список имён каталога отдаёт `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
+ на вкладке портала.
49
28
 
50
29
  ---
51
30
 
52
- ## 2. Формат ответа (важно!)
31
+ ## 2. Формат ответа
53
32
 
54
33
  Ajax-контроллеры (`/bitrix/services/main/ajax.php`) отвечают HTTP 200 с конвертом:
55
34
 
@@ -59,217 +38,135 @@ Ajax-контроллеры (`/bitrix/services/main/ajax.php`) отвечают
59
38
  "errors": [] } // ПУСТОЙ массив при успехе! (в JS [] — truthy)
60
39
  ```
61
40
 
62
- - Успех определяется `status:"success"` и/или **пустым** `errors`. Непустой `errors` / `status:"error"` /
63
- top-level `error` — реальная ошибка (мост маппит это в `ok:false`).
64
- - 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`.
65
44
 
66
45
  ---
67
46
 
68
- ## 3. Транспорт: form vs json (различается по методу!)
69
-
70
- У каждой записи каталога есть **`bodyType`** — как мост кодирует тело:
71
-
72
- - **`bodyType: "form"`** (по умолчанию) — `application/x-www-form-urlencoded`; вложенность кодируется
73
- PHP-стилем (`filter[STATUS]=2`, `select[0]=ID`). Объект в `params` рекурсивно разворачивается в такие
74
- ключи. Классические `tasks.task.*`, мессенджер `im.v2.*`, REST — всё это форма.
75
- - **`bodyType: "json"`** — тело уходит как `JSON.stringify(params)` с `Content-Type: application/json`.
76
- Так работают **`tasks.v2.*`** (напр. `tasks.v2.Task.get` ждёт `{"task":{"id":N}}`) и `ui.entityselector.*`.
77
- `params` при этом сохраняет вложенную структуру как есть (не разворачивается в bracket-ключи).
78
-
79
- > Историческая заметка: раньше мост умел только форму, и `tasks.v2.*` были непригодны. Теперь
80
- > поддержаны — в записи каталога ставь `"bodyType": "json"`, и мост отправит их корректно.
81
-
82
- **CSRF / sessid:** мост всегда шлёт заголовок **`X-Bitrix-Csrf-Token: <sessid>`** (свежий
83
- `BX.bitrix_sessid()`). Form-тела дополнительно несут `sessid` полем в теле (legacy REST это требует);
84
- JSON-тела несут sessid **только** в заголовке. Ответ `invalid_csrf`/`invalid_authentication` = сессия
85
- протухла (перелогинься в браузере).
86
-
87
- | Метод (action / endpoint) | bodyType | Где `select`/`filter`/`order` | Пагинация |
88
- |---|---|---|---|
89
- | `tasks.task.list` | form | **верхний уровень**: `select[]`, `filter{}`, `order{}` | `start` (сдвиг, шаг 50) |
90
- | `tasks.task.get` | form | верхний уровень: `taskId`, `select[]` | — |
91
- | `tasks.v2.Task.get` | **json** | `{"task": {"id": N}}` | — |
92
- | `tasks.v2.Scrum.getTaskInfo` | **json** | `{"taskId": N}` | — |
93
- | `tasks.v2.File.listObjects` | **json** | `{"ids": [N,…]}` | — |
94
- | `tasks.v2.Task.View.User.count` | **json** | `{"task": {"id": N}}` | — |
95
- | `tasks.v2.Task.Relation.Child.list` | **json** | `{"taskId": N, "withIds":true, "navigation":{"size":N}}` | `navigation` |
96
- | `tasks.v2.Task.Relation.Related.list` | **json** | `{"taskId": N, …}` (как Child) | `navigation` |
97
- | `socialnetwork.api.workgroup.list` | form | **верхний уровень**: `select[]`, `order{}`, `filter{}` | `start` (шаг 50; `start=(N-1)*50`, `-1` — ответ без `total`) 📖 |
98
- | `socialnetwork.api.workgroup.get` | form | **обёрнуто**: `params[groupId]`, `params[select][]` | — |
99
- | `/rest/im.recent.list.json` | form (rest) | плоско: `LIMIT`, `SKIP_OPENLINES`, `UNREAD_ONLY`, … | `LIMIT` |
100
- | `/rest/im.user.get.json` | form (rest) | плоско: `ID` | — |
101
- | `/rest/im.chat.get.json` | form (rest) | плоско: `ENTITY_TYPE`, `ENTITY_ID` | — |
102
- | `im.v2.Recent.load` | form | плоско: `limit`, `filter[recentSection]`, `filter[unread]`, `filter[parentId]` | `im.v2.Recent.tail` |
103
- | `im.v2.Recent.tail` | form | плоско: `limit`, `filter[lastMessageDate]` (курсор), `filter[recentSection]` | `filter[lastMessageDate]` |
104
- | `im.v2.Chat.load` | form | плоско: `dialogId`\|`chatId`, `messageLimit` | — |
105
- | `im.v2.Chat.getDialogId` | form | плоско: `externalId` | — |
106
- | `im.v2.Chat.Message.list` | form | плоско: `chatId`, `limit` | только последняя страница — назад **не листает**, используй `.tail` |
107
- | `im.v2.Chat.Message.tail` | form | плоско: `chatId`, `limit`, `filter[lastId]`, `order[id]` | **см. раздел 6** |
108
- | `im.v2.Chat.Message.read` | form | плоско: `chatId`, `ids[]`, `actionUuid` | — (мутирующий) |
109
- | `im.v2.Chat.readAll` | form | без параметров | — (мутирующий) |
110
- | `ui.entityselector.load` | **json** | `{"dialog": {entities,preselectedItems,…}}` | — |
111
- | `ui.entityselector.doSearch` | **json** | `{"dialog": {…}, "searchQuery": {"query": "...", "queryWords": ["..."]}}` | — |
112
-
113
- ---
47
+ ## 3. Каталог: имя → action → params
114
48
 
115
- ## 4. Каталог: имя → action → параметры
49
+ Форма `params` различается по методу: где-то плоско, где-то вложенно, где-то обёрнуто в `params[…]` —
50
+ колонка «params» показывает точную форму (кодирование тела мост берёт на себя).
116
51
 
117
- | Имя (`actions.json`) | action / endpoint | bodyType | Ключевые params |
52
+ | Имя (`bitrix_call`) | action / endpoint | params | Пагинация |
118
53
  |---|---|---|---|
119
- | `tasks.list` | `tasks.task.list` | form | `filter{RESPONSIBLE_ID,REAL_STATUS,GROUP_ID,…}`, `select[]`, `order{}`, `start` |
120
- | `task.get` | `tasks.task.get` | form | `taskId`, `select[]` |
121
- | `task.v2.get` | `tasks.v2.Task.get` | json | `task.id` (id задачи) |
122
- | `task.scrum.info` | `tasks.v2.Scrum.getTaskInfo` | json | `taskId` |
123
- | `task.files` | `tasks.v2.File.listObjects` | json | `ids` (массив id) |
124
- | `task.views.count` | `tasks.v2.Task.View.User.count` | json | `task.id` (id задачи) |
125
- | `task.subtasks` | `tasks.v2.Task.Relation.Child.list` | json | `taskId`, `navigation{size}` |
126
- | `task.related` | `tasks.v2.Task.Relation.Related.list` | json | `taskId`, `navigation{size}` |
127
- | `projects.list` | `socialnetwork.api.workgroup.list` | form | `select[]`, `order{}`, `filter{}` |
128
- | `projects.get` | `socialnetwork.api.workgroup.get` | form | `params[groupId]`, `params[select][]` |
129
- | `chats.recent` | `/rest/im.recent.list.json` | form | `LIMIT`, `UNREAD_ONLY`, `SKIP_OPENLINES` |
130
- | `recent.load` | `im.v2.Recent.load` | form | `filter[recentSection]` (`tasksTask`/`collab`/…), `limit`, `filter[unread]` |
131
- | `recent.tail` | `im.v2.Recent.tail` | form | `filter[lastMessageDate]`, `filter[recentSection]`, `limit` |
132
- | `chat.load` | `im.v2.Chat.load` | form | `dialogId`\|`chatId`, `messageLimit` |
133
- | `chat.dialogId` | `im.v2.Chat.getDialogId` | form | `externalId` (напр. `"sg"+groupId`) |
134
- | `chat.messages` | `im.v2.Chat.Message.list` | form | `chatId`, `limit` (последние; вглубь — `chat.messages.tail`) |
135
- | `chat.messages.tail` | `im.v2.Chat.Message.tail` | form | `chatId`, `filter[lastId]`, `order[id]`, `limit` |
136
- | `chat.message.read` | `im.v2.Chat.Message.read` | form | `chatId`, `ids[0]`, `actionUuid` |
137
- | `chat.read.all` | `im.v2.Chat.readAll` | form | — (⚠ мутирующий) |
138
- | `im.user.get` | `/rest/im.user.get.json` | form | `ID` |
139
- | `im.chat.get` | `/rest/im.chat.get.json` | form | `ENTITY_TYPE`, `ENTITY_ID` (напр. `TASKS_TASK`+taskId) |
140
- | `entityselector.load` | `ui.entityselector.load` | json | `dialog` (объект) |
141
- | `entityselector.search` | `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 |
142
78
 
143
79
  ---
144
80
 
145
- ## 5. Справочник полей
81
+ ## 4. Справочник полей
146
82
 
147
83
  **Задача** (`tasks.task.*`) — поля для `select`/`filter` в UPPER_CASE: `ID`, `TITLE`, `DESCRIPTION`,
148
84
  `STATUS`, `REAL_STATUS`, `RESPONSIBLE_ID`, `CREATED_BY`, `CREATED_DATE`, `CHANGED_DATE`, `DEADLINE`,
149
- `CLOSED_DATE`, `PRIORITY`, `GROUP_ID`, `TAGS`, `TIME_ESTIMATE`, **`CHAT_ID`** (id im-чата обсуждения
150
- задачи — прямой резолвер `taskId → chatId`, см. §6.3), `UF_*`. Ответ ajax также подкладывает объекты
151
- `group`, `responsible`, `creator`, `action`.
152
-
153
- 📖 офиц. (`tasks.task.get` / `tasks.task.list`) — полный набор полей задачи, среди прочего: `parentId`,
154
- `stageId`, `sprintId`, `backlogId`, `commentsCount`, `serviceCommentsCount`, `timeEstimate`,
155
- `timeSpentInLogs`, `favorite`, `flowId`, `mark`, `accomplices[]`, `auditors[]`, `checklist{}`,
156
- `subStatus`. В официальном REST-ответе поля приходят **camelCase** (`groupId`, `responsibleId`), тогда
157
- как в `select`/`filter` они всегда **UPPER_CASE**; наш ajax-вариант может отдавать иначе — сверяй
158
- реверсом. Доп. вычисления включаются флагом `params{WITH_TIMER_INFO, WITH_RESULT_INFO, WITH_PARSED_DESCRIPTION}`.
159
-
160
- Статусы (`REAL_STATUS` — «настоящее» числовое состояние): `1` Новая · `2` Ждёт выполнения ·
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` Ждёт выполнения ·
161
95
  `3` Выполняется · `4` Ждёт контроля · `5` Завершена · `6` Отложена · `7` Отклонена. Поле `STATUS`
162
96
  поверх этого несёт **мета-состояния** отображения (почти просрочена / не просмотрена / просрочена),
163
- поэтому «работает ли задача сейчас» фильтруй по **`REAL_STATUS`** (напр. `{"REAL_STATUS":3}`), а не
164
- по `STATUS` (📖 `tasks.task.list`).
97
+ поэтому «работает ли задача сейчас» фильтруй по **`REAL_STATUS`** (напр. `{"REAL_STATUS":3}`) 📖.
98
+ Асимметрия: в **`filter`** работает `REAL_STATUS`, а в **`select`** его указывать бесполезно — поле молча
99
+ не вернётся; чтобы получить состояние, проси `select:["STATUS"]` → в ответе будет ключ `status` со
100
+ значением по той же шкале 1..7 (у обёртки `bitrix_tasks_list` он уже в дефолтном `select`).
165
101
 
166
- **Фильтр `tasks.task.list`.** Перед именем поля ставится оператор: `!` (не равно / исключить),
102
+ **Фильтр `tasks.task.list`** — оператор ставится перед именем поля: `!` (не равно / исключить),
167
103
  `<`, `<=`, `>`, `>=`, `%` (LIKE-подстрока). Напр. `{"!REAL_STATUS":5}` — все **незакрытые**,
168
- `{">=DEADLINE":"2026-07-01"}` — дедлайн не раньше даты, `{"%TITLE":"карта"}` — по подстроке названия.
169
- Фильтруемые поля: `ID`, `PARENT_ID`, `GROUP_ID`, `CREATED_BY`, `RESPONSIBLE_ID`, `ACCOMPLICE`,
170
- `AUDITOR`, `REAL_STATUS`, `STATUS`, `PRIORITY`, `TAG`, `STAGE_ID`, `SPRINT_ID`, `BACKLOG_ID`,
171
- `DEADLINE`, `*_DATE`, `UF_CRM_TASK`.
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`.
172
107
 
173
108
  > **«Мои задачи» ≠ только `RESPONSIBLE_ID`.** Роли участника раздельны: ответственный
174
109
  > (`RESPONSIBLE_ID`), соисполнитель (`ACCOMPLICE`), наблюдатель (`AUDITOR`), постановщик
175
- > (`CREATED_BY`). Единого «любая роль» поля в `list` нет — чтобы собрать **все** свои задачи,
176
- > объедини результаты нескольких вызовов (минимум `RESPONSIBLE_ID` + `ACCOMPLICE`). Тонкость:
177
- > в **`filter`** роли в **единственном** числе (`ACCOMPLICE`/`AUDITOR`), а в **`select`** — во
178
- > **множественном** (`ACCOMPLICES`/`AUDITORS`). Не путай.
179
-
180
- > **Scrum-задачи (спринты).** Поле `SPRINT_ID` на задаче и жёсткий фильтр `filter[SPRINT_ID]`
181
- > могут расходиться: задача числится за спринтом по своему полю, но не попадает в выборку по фильтру
182
- > (вероятно, возвращена в бэклог или в иной stage). Если по фильтру «маловато» — перепроверь без него
183
- > и отфильтруй по `SPRINT_ID`/`STAGE_ID` на стороне агента. Точного API колонок scrum-доски
184
- > (`STAGE_ID` → имя колонки) в текущем каталоге нет — это кандидат на реверс.
185
-
186
- **Задача v2** (`tasks.v2.*`): id задачи передаётся вложенно — **`{"task": {"id": N}}`** (у `Task.get` и
187
- `Task.View.User.count`), а у `Scrum.getTaskInfo` и `Relation.*` — плоско `{"taskId": N}`. Это отдельная
188
- подсистема scrum-борда; структуру ответа сверь реверсом — в захвате видны только параметры запроса.
189
-
190
- **Группа/проект — список** (`workgroup.list`, camelCase в ответе): `ID`, `NAME`, `DESCRIPTION`,
191
- `NUMBER_OF_MEMBERS`, `OWNER_ID`, `DATE_CREATE`, `PROJECT` (Y/N), `TYPE`.
192
- 📖 офиц. (`socialnetwork.api.workgroup.list`) — фильтруемые поля: `ID`, `NAME`, `OWNER_ID`, `ACTIVE`,
193
- `VISIBLE`, `OPENED`, `CLOSED`, `PROJECT` (Y=проект/N=группа), `SUBJECT_ID`, `SITE_ID`, `DATE_CREATE`,
194
- `DATE_UPDATE`, `DATE_ACTIVITY`; операторы фильтра `>= > <= < % =% %= !% != !`; без `select` вернётся
195
- только `ID`. Старый публичный аналог — `sonet_group.get` (те же поля/пагинация, ключи `FILTER`/`ORDER`
196
- капсом).
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
+ `>= > <= < % =% %= !% != !`.
197
125
  **Группа — карточка** (`workgroup.get`): плюс `OWNER_DATA`, `SUBJECT_DATA`, `MEMBERS[]`,
198
- `MODERATOR_MEMBERS[]`, `CHAT_ID`, `DIALOG_ID`, `IMAGE_ID`, UF-поля.
126
+ `MODERATOR_MEMBERS[]`, **`CHAT_ID`**, `DIALOG_ID`, `IMAGE_ID`, UF-поля.
199
127
 
200
128
  **Недавний чат** (`im.recent.list`): `id` (это dialogId: число = юзер, `"chat"+N` = чат), `chat_id`,
201
- `type` (`user`/`chat`), `title`, `message{id,text,file,author_id,date,status,uuid}`, `counter` (число
202
- непрочитанных), `unread` (bool), `last_id`, `pinned`, `date_update`, `date_last_activity`, `user{…}`,
203
- `chat{… entity_type, entity_id, owner, …}`. Есть ли ещё страницы — флаги `hasMore`/`hasMorePages`
204
- (📖 `im.recent.list`).
205
- **Недавние по секции** (`im.v2.Recent.load`): фильтр `filter[recentSection]` — `default` (обычные
206
- диалоги), **`tasksTask` (чаты задач)**, `collab`/`collabDefault` (коллабы). Листание вглубь —
207
- `im.v2.Recent.tail` с курсором `filter[lastMessageDate]` (ISO-дата последнего элемента страницы).
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-дата последнего элемента страницы).
135
+
208
136
  **Карточка юзера** (`im.user.get`): `id`, `name`, `first_name`, `last_name`, `avatar`, `work_position`,
209
137
  `gender`, `status`, `online` — резолв автора сообщения (`authorId`) в имя.
210
- **Сообщение** (`im.v2.Chat.Message.*`): `id`, `chatId`, `authorId`, `date`, `text`, `params`, `viewed`.
211
- Ответ также несёт `users[]` (участники), `additionalMessages[]`, `hasPrevPage`/`hasNextPage`.
212
- `chatId` берётся из `im.recent.list` (`chat_id`) или из `workgroup.get` (`CHAT_ID`/`DIALOG_ID`).
213
-
214
- **Адресация чата (`chat.load`): `dialogId` vs `chatId`.** `chat.load` принимает любой из двух:
215
- - `dialogId` = **ID пользователя** (число, напр. `11`) → **личный чат 1-на-1** с этим пользователем.
216
- - `dialogId` = **`"chat"+CHAT_ID`** (напр. `"chat7111"`) → **групповой чат** (проекта/задачи/канала).
217
- - `chatId` = числовой `CHAT_ID` — то же, что групповой `dialogId`, но без префикса.
218
138
 
219
- 📖 офиц. формат `DIALOG_ID` (`im.dialog.get`): `XXX` — личный (userId), `chatXXX` — чат, **`sgXXX`** —
220
- чат соцгруппы/проекта напрямую (напр. `sg15`). В публичном REST это единый параметр; наш внутренний
221
- `im.v2.Chat.load` надёжно принимает `dialogId`/`chatId`, а форму `sgXXX` для него сверь реверсом —
222
- гарантированный путь к чату группы это `bitrix_chat_get_dialog_id { externalId:"sg<groupId>" }`.
223
-
224
- Ответ `chat.load` возвращает числовой `chatId` — используй его дальше для `chat.messages` /
225
- `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`.
226
141
 
227
142
  ---
228
143
 
229
- ## 6. Примеры вызовов
144
+ ## 5. Сценарии
230
145
 
231
146
  ```jsonc
232
147
  // открытые задачи ответственного 55, по дедлайну, вторая страница
233
148
  bitrix_tasks_list { "params": { "filter": {"RESPONSIBLE_ID":55,"REAL_STATUS":2},
234
149
  "order": {"DEADLINE":"asc"}, "start": 50 } }
235
150
 
236
- // карточка задачи с расширенным набором полей
237
- bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","TITLE","DESCRIPTION","TAGS","TIME_ESTIMATE"] } }
238
-
239
- // карточка задачи через v2-подсистему (JSON-тело; id вложен в task)
151
+ // карточка задачи с расширенным набором полей; v2-подсистема; файлы, просмотры, связи
152
+ bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","TITLE","DESCRIPTION","TAGS"] } }
240
153
  bitrix_task_get_v2 { "taskId": 4229 } // → { "task": { "id": 4229 } }
241
- bitrix_call { "name": "task.v2.get", "params": { "task": { "id": 4229 } } }
242
-
243
- // файлы задачи и счётчик просмотров (v2, JSON-тело); подзадачи/связанные
244
154
  bitrix_call { "name": "task.files", "params": { "ids": [4229] } }
245
- bitrix_call { "name": "task.views.count", "params": { "task": { "id": 4229 } } }
246
155
  bitrix_task_subtasks { "taskId": 4229 }
247
156
  bitrix_task_related { "taskId": 4229 }
248
157
 
249
- // проекты по дате создания; полная карточка группы
158
+ // проекты по дате создания; полная карточка группы (отдаёт CHAT_ID / DIALOG_ID)
250
159
  bitrix_projects_list { "params": { "order": {"DATE_CREATE":"desc"} } }
251
- bitrix_project_get { "groupId": 15 }
252
-
253
- // открыть чат (первые сообщения)
254
- bitrix_chat_load { "chatId": 485, "messageLimit": 50 }
255
-
256
- // ЛИСТАНИЕ ИСТОРИИ ВГЛУБЬ (старые сообщения): beforeId = минимальный id сообщения
257
- // из текущей страницы. Повторяй, уменьшая beforeId, до начала истории (order DESC — дефолт).
258
- bitrix_chat_history { "chatId": 485, "beforeId": 1861279 }
259
-
260
- // пометить сообщения прочитанными (МУТИРУЮЩИЙ). actionUuid генерируется автоматически.
261
- bitrix_chat_mark_read { "chatId": 40271, "ids": [1884131] }
160
+ bitrix_project_get { "groupId": 15 }
262
161
  ```
263
162
 
264
- ### 6.1. Мессенджер (IM): модель адресации и как получить `chatId`
163
+ ### 5.1. Мессенджер (IM): как получить `chatId`
265
164
 
266
- **IM** (`im.v2`) — встроенный мессенджер Bitrix24. Всё общение — это **чаты**: личные
267
- 1-на-1, групповые (проектов, каналов) и **чаты-обсуждения задач**. У каждого чата есть числовой
268
- **`chatId`** — это ключ ко всем операциям с сообщениями (`bitrix_chat_messages`,
269
- `bitrix_chat_history`, `bitrix_chat_mark_read`). Прежде чем читать сообщения, нужно **получить
270
- `chatId`** — почти любая ошибка агента здесь именно в этом шаге.
165
+ Всё общение — **чаты**: личные 1-на-1, групповые (проектов, каналов) и **чаты-обсуждения задач**.
166
+ Числовой **`chatId`** — ключ ко всем операциям с сообщениями (`bitrix_chat_messages`,
167
+ `bitrix_chat_history`, `bitrix_chat_mark_read`); почти любая ошибка агента здесь — на шаге его получения.
271
168
 
272
- **Три идентификатора одного чата** (не путай — годятся для разных операций):
169
+ **Три идентификатора одного чата** (годятся для разных операций):
273
170
 
274
171
  | Идентификатор | Что это | Пример | Куда передавать |
275
172
  |---|---|---|---|
@@ -277,173 +174,161 @@ bitrix_chat_mark_read { "chatId": 40271, "ids": [1884131] }
277
174
  | `dialogId` | адрес для ОТКРЫТИЯ: `userId` (личный 1-на-1) **или** `"chat"+chatId` (групповой) | `11`, `"chat485"` | `bitrix_chat_load` |
278
175
  | `externalId` | внешний ключ сущности → резолвится в `dialogId` | `"sg15"` (соцгруппа 15) | `bitrix_chat_get_dialog_id` |
279
176
 
280
- Ключевое: **личный чат с человеком = его `userId` в роли `dialogId`** (отдельного «id чата» у
281
- 1-на-1 знать не нужно). `bitrix_chat_load` принимает `dialogId` **или** `chatId` и всегда
282
- **возвращает числовой `chatId`** — дальше оперируешь им.
177
+ Ключевое: **личный чат с человеком = его `userId` в роли `dialogId`** (отдельного «id чата» у 1-на-1
178
+ знать не нужно). `bitrix_chat_load` принимает `dialogId` **или** `chatId` и всегда **возвращает
179
+ числовой `chatId`** — дальше оперируешь им.
283
180
 
284
- **Как получить `chatId` — по цели (дерево решений):**
181
+ **Как получить `chatId` — по цели:**
285
182
 
286
183
  | Хочу открыть чат… | Шаги |
287
184
  |---|---|
288
- | **с пользователем по ИМЕНИ** | `bitrix_entity_search { query:"дмитрий" }` → вернёт диалог с `dialogId` = его userId → `bitrix_chat_load { dialogId }` |
185
+ | **с пользователем по ИМЕНИ** | `bitrix_entity_search { query:"дмитрий" }` → диалог с `dialogId` = его userId → `bitrix_chat_load { dialogId }` |
289
186
  | **с пользователем, id известен** | `bitrix_chat_load { dialogId: <userId> }` (userId — из задачи `RESPONSIBLE_ID`/`CREATED_BY`, из `bitrix_chats_recent`, из `bitrix_user_get`) |
290
- | **обсуждение ЗАДАЧИ** | `bitrix_recent_load { section:"tasksTask" }` **или** `bitrix_entity_search { query:"<название>", section:"tasksTask" }` → `chatId`/`dialogId` → `bitrix_chat_load` (§6.3) |
187
+ | **обсуждение ЗАДАЧИ** | `bitrix_task_get` → `CHAT_ID` (§5.2) |
291
188
  | **ПРОЕКТА/группы** | `bitrix_task_get`→`GROUP_ID`→`bitrix_project_get`→`CHAT_ID`; либо `bitrix_chat_get_dialog_id { externalId:"sg<groupId>" }` |
292
- | **не знаю точно, какой** | `bitrix_chats_recent` (недавние, сопоставь по `title`/`user`) или `bitrix_entity_search { query }` (поиск по названию) |
189
+ | **не знаю точно, какой** | `bitrix_chats_recent` (недавние, сопоставь по `title`/`user`) или `bitrix_entity_search { query }` |
293
190
 
294
191
  > Почему не «просто список чатов»: `bitrix_chats_recent` (`im.recent.list`) отдаёт лишь недавние и
295
192
  > **без чатов задач**. Для поиска конкретного чата — `bitrix_entity_search`; для чатов задач —
296
193
  > `bitrix_recent_load { section:"tasksTask" }`.
297
194
 
298
- 📖 Публичные аналоги чтения сообщений (если нужно глубже разобраться в модели): `im.dialog.messages.get`
299
- — по умолчанию отдаёт последние (дефолт 20, макс 50), листание `LAST_ID` (старее) / `FIRST_ID` (новее);
300
- `imbot.v2 chat.getMessageContext` — ответ `messages[]` (oldest→newest) + `users[]` +
301
- `hasPrevPage`/`hasNextPage`, что **совпадает по форме** с нашими внутренними `im.v2.Chat.Message.*`.
302
-
303
- ### 6.2. Сценарий: чат с конкретным пользователем
304
-
305
- Личный чат 1-на-1 адресуется `dialogId` = **ID пользователя**. Id берётся из задачи
306
- (`RESPONSIBLE_ID`/`CREATED_BY`), из `bitrix_chats_recent`, либо резолвится поиском по имени (§6.4).
195
+ **Читать историю.** `bitrix_chat_messages` отдаёт только последнюю страницу; вглубь листает
196
+ `bitrix_chat_history` по курсору `beforeId` = минимальный id сообщения текущей страницы (повторяй,
197
+ уменьшая `beforeId`, до начала истории).
307
198
 
308
199
  ```jsonc
309
- // 1) открыть личный чат с пользователем 11 → в ответе будет числовой chatId
310
- bitrix_chat_load { "dialogId": 11 }
311
- // 2) листать его историю вглубь по chatId из ответа шага 1
200
+ bitrix_chat_load { "dialogId": 11 } // личный чат юзера 11 → в ответе chatId
312
201
  bitrix_chat_history { "chatId": 485, "beforeId": 1861279 }
202
+ bitrix_chat_mark_read { "chatId": 40271, "ids": [1884131] } // ⚠ мутирующий; actionUuid — автоматом
313
203
  ```
314
204
 
315
- ### 6.3. Сценарий: обсуждение (чат) конкретной задачи
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.*`.
316
209
 
317
- У каждой задачи есть свой im-чат-обсуждение. Самый надёжный путь — прямой резолв из карточки задачи;
318
- поиск по названию — запасной.
210
+ ### 5.2. Обсуждение (чат) конкретной задачи
319
211
 
320
- **А. Прямой — `CHAT_ID` из карточки задачи (предпочтительно).** `bitrix_task_get` возвращает `CHAT_ID`
321
- чата-обсуждения (он входит в дефолтный `select`). Это прямой `taskId → chatId` **без** поиска по
322
- названию и без риска промаха по тёзкам-заголовкам:
212
+ **А. Прямой резолв — предпочтительно.** `bitrix_task_get` возвращает `CHAT_ID` чата-обсуждения (входит
213
+ в дефолтный `select` 📖) — это `taskId → chatId` без поиска по названию и без промаха по тёзкам:
323
214
 
324
215
  ```jsonc
325
- bitrix_task_get { "taskId": 28373 } // в ответе CHAT_ID = id чата обсуждения
216
+ bitrix_task_get { "taskId": 28373 } // в ответе CHAT_ID
326
217
  bitrix_chat_load { "chatId": <CHAT_ID> }
327
218
  bitrix_chat_history { "chatId": <CHAT_ID>, "beforeId": <мин id страницы> }
328
219
  ```
329
220
 
330
- 📖 офиц.: в `tasks.task.get` поле `CHAT_ID` отдаётся по умолчанию (tasks-new.md). Общий
331
- резолвер-альтернатива — **`bitrix_entity_chat`** (обёртка над `im.chat.get`): по паре
332
- `entityType`/`entityId` отдаёт chatId связанного объекта — для задачи `entityType="TASKS_TASK"`,
333
- `entityId=<taskId>`; так же адресуются чаты `SONET_GROUP` (группа/проект), `CRM`, `CALENDAR`, `MAIL`,
334
- `VIDEOCONF`, `CALL`:
221
+ Общая альтернатива — **`bitrix_entity_chat`** (обёртка над `im.chat.get`): chatId любого связанного
222
+ объекта по паре `entityType`/`entityId`; кроме `TASKS_TASK` так же адресуются `SONET_GROUP`
223
+ (группа/проект), `CRM`, `CALENDAR`, `MAIL`, `VIDEOCONF`, `CALL`:
335
224
 
336
225
  ```jsonc
337
226
  bitrix_entity_chat { "entityType": "TASKS_TASK", "entityId": 28373 } // → chatId
338
- bitrix_chat_load { "chatId": <chatId> }
339
227
  ```
340
228
 
341
- **Б. Через список чатов задач.** `im.v2.Recent.load` с секцией `tasksTask` отдаёт чаты задач
342
- (в `bitrix_chats_recent` / `/rest/im.recent.list` их НЕТ). Листать вглубь — `bitrix_recent_tail`.
229
+ **Б. Через список чатов задач** (секция `tasksTask`; в `bitrix_chats_recent` их НЕТ):
343
230
 
344
231
  ```jsonc
345
232
  bitrix_recent_load { "section": "tasksTask" } // → чаты задач с chatId/dialogId
346
233
  bitrix_recent_tail { "section": "tasksTask", "lastMessageDate": "2026-06-29T17:25:38+03:00" }
347
- // открыть найденный чат и листать историю
348
- bitrix_chat_load { "dialogId": "chat38849" } // или { "chatId": 38849 }
349
- bitrix_chat_history { "chatId": 38849, "beforeId": 1722353 }
234
+ bitrix_chat_load { "dialogId": "chat38849" } // или { "chatId": 38849 }
350
235
  ```
351
236
 
352
- **В. Поиском по названию задачи** — `bitrix_entity_search` с секцией `tasksTask` (запасной, матч по тексту):
353
-
354
- ```jsonc
355
- bitrix_entity_search { "query": "трекер", "section": "tasksTask" }
356
- ```
237
+ **В. Поиском по названию задачи** (запасной, матч по тексту):
238
+ `bitrix_entity_search { "query": "трекер", "section": "tasksTask" }`
357
239
 
358
240
  **Чат ПРОЕКТА задачи** (обсуждение группы, не самой задачи) — через `GROUP_ID`:
359
241
 
360
242
  ```jsonc
361
243
  bitrix_task_get { "taskId": 4229, "params": { "select": ["ID","GROUP_ID"] } }
362
244
  bitrix_project_get { "groupId": 15 } // отдаёт CHAT_ID / DIALOG_ID
363
- bitrix_chat_load { "dialogId": "chat7111" }
364
245
  bitrix_chat_get_dialog_id { "externalId": "sg15" } // либо резолв dialogId соцгруппы напрямую
365
246
  ```
366
247
 
367
- ### 6.4. Сценарий: поиск чата / пользователя
248
+ ### 5.3. Поиск чата / пользователя
368
249
 
369
- Текстовый поиск — `bitrix_entity_search` (обёртка над `ui.entityselector.doSearch`): сам собирает
370
- диалог `IM_CHAT_SEARCH` и `searchQuery` из строки. `ui.entityselector.*` — **внутренний UI-метод, в
371
- публичных доках его нет** (форма запроса/ответа — только реверс); публичные аналоги для поиска людей —
372
- `user.get`/`user.search`, для чатов — `im.recent.list` + фильтрация на стороне агента.
250
+ `bitrix_entity_search` (обёртка над внутренним `ui.entityselector.doSearch`) сам собирает диалог
251
+ `IM_CHAT_SEARCH` и `searchQuery` из строки. Публичные аналоги: люди — `user.get`/`user.search`, чаты —
252
+ `im.recent.list` + фильтрация у себя.
373
253
 
374
254
  ```jsonc
375
255
  bitrix_entity_search { "query": "дмитрий" } // среди всех чатов/диалогов
376
256
  bitrix_entity_search { "query": "трекер", "section": "tasksTask" } // среди чатов задач
257
+ bitrix_user_get { "userId": 11 } // резолв authorId → имя
377
258
  ```
378
259
 
379
- Быстрый путь без поиска — недавние чаты и фильтрация на стороне агента; карточка автора по 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 }`.
380
268
 
381
269
  ```jsonc
382
- bitrix_chats_recent { } // сопоставь по title / user
383
- bitrix_recent_load { "section": "tasksTask" }
384
- 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 } // продолжить вглубь
385
272
  ```
386
273
 
387
- ### 6.5. Легаси-комментарии задачи (HTML-поддомен — пока НЕ в мосте)
274
+ Ответ: `calls[] { callId, startedAt, startedBy, startMessageId, events[], summaryMessageId }` плюс
275
+ `scannedPages` / `oldestScannedMessageId` / **`reachedHistoryStart`** — если он `false`, история
276
+ дочитана НЕ до конца, продолжай с `beforeId: <oldestScannedMessageId>`.
388
277
 
389
- У задач два независимых фида обсуждения:
390
- - **im.v2-чат** — структурный JSON, читается уже сейчас (§6.3); обычно этого достаточно.
391
- - **Форумные легаси-комментарии** — рендерятся как **HTML** в side-slider'е. Это отдельный
392
- «HTML-поддомен» внутреннего API, который **текущий мост не поддерживает** (блокеры ниже).
278
+ **2) Взять всё по звонку.**
393
279
 
394
- **Как грузятся (реверс).** Первая страница — GET HTML-страницы iframe (сервер тут же минтит подпись):
395
-
396
- ```
397
- 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" } // всегда в файл, даже короткая
398
285
  ```
399
286
 
400
- Листание — 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
+ сессии пользователя.
401
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 }
402
320
  ```
403
- POST /bitrix/services/main/ajax.php?mode=class&c=bitrix:forum.comments&action=navigateComment
404
- form: AJAX_POST=Y, ENTITY_XML_ID=TASK_<taskId>, taskId=<taskId>, MODE=LIST,
405
- FILTER[<ID]=<курсор: id, ДО которого грузить старые>, PAGEN_1=1,
406
- signedParameters=<подписанный blob>, IFRAME=Y, IFRAME_TYPE=SIDE_SLIDER
407
- headers: bx-ajax: true, x-bitrix-site-id: s1 (плюс обычный X-Bitrix-Csrf-Token)
408
- ```
409
-
410
- `signedParameters` — base64 PHP-массива + HMAC-подпись сервера; кодирует
411
- `FORUM_ID, ENTITY_TYPE=TK, ENTITY_ID=<taskId>, ENTITY_XML_ID=TASK_<taskId>, …`. **Подделать нельзя** —
412
- берётся из HTML первой страницы.
413
-
414
- **Почему пока не в каталоге (3 блокера):**
415
- 1. Мост парсит ответ только как JSON (`extension/src/connector.ts` → `resp.json()`) — HTML бросит исключение.
416
- 2. Path-параметр в URL (`/task/comments/<taskId>/`) — записи каталога статичны, шаблонов пути нет.
417
- 3. `signedParameters` + заголовки `bx-ajax`/`x-bitrix-site-id` — нужен предварительный GET HTML-страницы.
418
321
 
419
- **Чтобы подключить** (будущая работа): научить мост возвращать text/HTML при не-JSON ответе и
420
- поддержать path-шаблон эндпоинта; затем обёртка `bitrix_task_comments { taskId }` (GET первой
421
- страницы), а листание — через `navigateComment` с выпарсенным `signedParameters`.
322
+ Ответ — **путь, а не содержимое**: `{ path, bytes, contentType, fileName }` (гигабайты не попадают в
323
+ контекст). По умолчанию файл ложится в `~/.bitrix24-mcp-bridge/downloads/`; `savePath` без ведущего `/`
324
+ трактуется как имя внутри этой папки. Потолок — 512 МБ.
422
325
 
423
- > Тот же HTML-поддомен: **детали звонка** — `GET /call/detail/<callId>?IFRAME=Y&IFRAME_TYPE=SIDE_SLIDER`
424
- > (отдаёт HTML). Ограничения идентичны.
326
+ Тонкости:
327
+ - **Граница безопасности — origin**: скачивается только с origin сконфигурированного портала.
328
+ - **Имя даёт портал** (`Content-Disposition`), поэтому без `savePath` угадывать его не нужно.
329
+ - **Повторное скачивание** того же файла падает с `already exists` — передай `overwrite: true`.
425
330
 
426
331
  ---
427
332
 
428
- ## 7. Как расширить (новый домен: календарь, диск, CRM, …)
429
-
430
- > Совет: сперва проверь, задокументирован ли метод в 📖 [`apidocs.bitrix24.com`](https://apidocs.bitrix24.com)
431
- > (context7: `/bitrix24/b24restdocs`). Для публичных `*.list`/`*.get` там готовые поля, фильтры и
432
- > пагинация — реверс тогда нужен лишь чтобы подтвердить транспорт (form/json) и конверт ответа. Для
433
- > внутренних `im.v2.*`/`tasks.v2.*`/UI-методов доков нет — только реверс (`docs/reconnaissance.md`).
434
-
435
- 1. Сними реальные вызовы (`docs/reconnaissance.md` — авто-запись `bun run capture` или HAR →
436
- `bun run catalog:draft`). Черновик `actions.draft.json` накапливает **все уникальные комбинации
437
- параметров** на экшен и проставляет `bodyType` (form/json) автоматически.
438
- 2. Определи транспорт по разделу 3: если черновик показал `"bodyType": "json"` — перенеси его в запись.
439
- 3. Добавь запись в `actions.json` (может быть мутирующей — каталог это допускает).
440
- 4. Если удобно — оберни в типизированный инструмент в `src/tools/register.ts` (обёртка регистрируется
441
- только если её `catalogName` есть в каталоге).
442
- 5. **`bun run sync:runtime`** — доставь изменения живому daemon: пересобрать бандл, скопировать
443
- репо-`actions.json` в `~/.bitrix24-mcp-bridge/actions.json` и погасить daemon. Без этого агент
444
- новых имён/инструментов не увидит (daemon читает runtime-папку, не репо). Проверь через
445
- `bitrix_call { name, params }` или новую обёртку.
446
-
447
- > `actions.json` / `actions.draft.json` — данные конкретного портала (gitignored). Этот файл
448
- > (`api-notes.md`) — переносимые знания о самом API; держи его в актуальном состоянии при добавлении
449
- > новых методов.
333
+ Расширение каталога новыми методами, транспорт (`bodyType`, `responseType`, path-параметры) и
334
+ доставка правок в живой daemon — `docs/reconnaissance.md`.