@ykurilov/agent-chat-messengers 0.0.0-stage → 0.56.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +900 -0
  2. package/LICENSE +21 -0
  3. package/README.md +381 -2
  4. package/dist/chunk-4QQ62ZKT.js +197 -0
  5. package/dist/chunk-4QQ62ZKT.js.map +1 -0
  6. package/dist/chunk-C6IGB3R2.js +9 -0
  7. package/dist/chunk-C6IGB3R2.js.map +1 -0
  8. package/dist/index.js +2748 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/max.js +308 -0
  11. package/dist/max.js.map +1 -0
  12. package/dist/postgres.js +367 -0
  13. package/dist/postgres.js.map +1 -0
  14. package/dist/react.js +184 -0
  15. package/dist/react.js.map +1 -0
  16. package/dist/sqlite.js +326 -0
  17. package/dist/sqlite.js.map +1 -0
  18. package/dist/telegram.js +297 -0
  19. package/dist/telegram.js.map +1 -0
  20. package/dist/types/account-linking.d.ts +35 -0
  21. package/dist/types/answer-delivery.d.ts +67 -0
  22. package/dist/types/bot-commands.d.ts +11 -0
  23. package/dist/types/bounded-body.d.ts +18 -0
  24. package/dist/types/button-actions.d.ts +139 -0
  25. package/dist/types/chat-notices.d.ts +8 -0
  26. package/dist/types/chat-port.d.ts +152 -0
  27. package/dist/types/conversation-policy.d.ts +60 -0
  28. package/dist/types/create-messengers.d.ts +20 -0
  29. package/dist/types/group-chats.d.ts +46 -0
  30. package/dist/types/inbound-media.d.ts +37 -0
  31. package/dist/types/index.d.ts +10 -0
  32. package/dist/types/markdown-to-messenger.d.ts +28 -0
  33. package/dist/types/max/index.d.ts +27 -0
  34. package/dist/types/max/max-adapter.d.ts +10 -0
  35. package/dist/types/max/max-client.d.ts +22 -0
  36. package/dist/types/max/max-inbound.d.ts +55 -0
  37. package/dist/types/max.d.ts +2 -0
  38. package/dist/types/messenger-adapter.d.ts +128 -0
  39. package/dist/types/messenger-api.d.ts +51 -0
  40. package/dist/types/messenger-files.d.ts +27 -0
  41. package/dist/types/messenger-gateway.d.ts +55 -0
  42. package/dist/types/messenger-texts.d.ts +118 -0
  43. package/dist/types/outbound-queue.d.ts +48 -0
  44. package/dist/types/pending-deliveries.d.ts +30 -0
  45. package/dist/types/polling-lease.d.ts +25 -0
  46. package/dist/types/postgres.d.ts +17 -0
  47. package/dist/types/react/messengers-labels.d.ts +31 -0
  48. package/dist/types/react/messengers-skin.d.ts +41 -0
  49. package/dist/types/react/use-messenger-links.d.ts +21 -0
  50. package/dist/types/react.d.ts +5 -0
  51. package/dist/types/sqlite.d.ts +11 -0
  52. package/dist/types/store/memory-store.d.ts +3 -0
  53. package/dist/types/store/migration-settings.d.ts +4 -0
  54. package/dist/types/store/postgres-schema.d.ts +1053 -0
  55. package/dist/types/store/postgres-store.d.ts +9 -0
  56. package/dist/types/store/sqlite-schema.d.ts +1110 -0
  57. package/dist/types/store/sqlite-store.d.ts +4 -0
  58. package/dist/types/task-cards.d.ts +25 -0
  59. package/dist/types/task-ready-ping.d.ts +27 -0
  60. package/dist/types/telegram/index.d.ts +26 -0
  61. package/dist/types/telegram/telegram-adapter.d.ts +9 -0
  62. package/dist/types/telegram/telegram-client.d.ts +19 -0
  63. package/dist/types/telegram/telegram-inbound.d.ts +82 -0
  64. package/dist/types/telegram.d.ts +2 -0
  65. package/dist/types/types.d.ts +375 -0
  66. package/dist/types/vk/index.d.ts +28 -0
  67. package/dist/types/vk/vk-adapter.d.ts +18 -0
  68. package/dist/types/vk/vk-client.d.ts +22 -0
  69. package/dist/types/vk/vk-inbound.d.ts +15 -0
  70. package/dist/types/vk.d.ts +2 -0
  71. package/dist/types/voice-replies.d.ts +32 -0
  72. package/dist/vk.js +364 -0
  73. package/dist/vk.js.map +1 -0
  74. package/drizzle/postgres/0000_init.sql +72 -0
  75. package/drizzle/postgres/0001_pending_deliveries.sql +14 -0
  76. package/drizzle/postgres/0002_conversations_current_chat_idx.sql +1 -0
  77. package/drizzle/postgres/0003_inbound_updates_recovery.sql +7 -0
  78. package/drizzle/postgres/meta/0000_snapshot.json +514 -0
  79. package/drizzle/postgres/meta/0001_snapshot.json +624 -0
  80. package/drizzle/postgres/meta/0002_snapshot.json +639 -0
  81. package/drizzle/postgres/meta/0003_snapshot.json +703 -0
  82. package/drizzle/postgres/meta/_journal.json +34 -0
  83. package/drizzle/sqlite/0000_init.sql +72 -0
  84. package/drizzle/sqlite/0001_pending_deliveries.sql +13 -0
  85. package/drizzle/sqlite/0002_conversations_current_chat_idx.sql +1 -0
  86. package/drizzle/sqlite/0003_inbound_updates_recovery.sql +7 -0
  87. package/drizzle/sqlite/meta/0000_snapshot.json +462 -0
  88. package/drizzle/sqlite/meta/0001_snapshot.json +545 -0
  89. package/drizzle/sqlite/meta/0002_snapshot.json +552 -0
  90. package/drizzle/sqlite/meta/0003_snapshot.json +604 -0
  91. package/drizzle/sqlite/meta/_journal.json +34 -0
  92. package/package.json +110 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,900 @@
1
+ # @ykurilov/agent-chat-messengers
2
+
3
+ ## 0.56.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [1eadba9]
8
+ - Updated dependencies [24c313e]
9
+ - @ykurilov/agent-chat-react@0.56.0
10
+ - @ykurilov/agent-chat-core@0.56.0
11
+
12
+ ## 0.55.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies [51f7b38]
17
+ - Updated dependencies [cdc7dd7]
18
+ - @ykurilov/agent-chat-core@0.55.0
19
+ - @ykurilov/agent-chat-react@0.55.0
20
+
21
+ ## 0.54.0
22
+
23
+ ### Patch Changes
24
+
25
+ - Updated dependencies [c8fb0dd]
26
+ - Updated dependencies [73d3aaa]
27
+ - Updated dependencies [fa90219]
28
+ - @ykurilov/agent-chat-react@0.54.0
29
+ - @ykurilov/agent-chat-core@0.54.0
30
+
31
+ ## 0.53.0
32
+
33
+ ### Patch Changes
34
+
35
+ - Updated dependencies [b6a7786]
36
+ - @ykurilov/agent-chat-core@0.53.0
37
+ - @ykurilov/agent-chat-react@0.53.0
38
+
39
+ ## 0.52.0
40
+
41
+ ### Patch Changes
42
+
43
+ - @ykurilov/agent-chat-core@0.52.0
44
+ - @ykurilov/agent-chat-react@0.52.0
45
+
46
+ ## 0.51.0
47
+
48
+ ### Patch Changes
49
+
50
+ - Updated dependencies [41b1726]
51
+ - @ykurilov/agent-chat-core@0.51.0
52
+ - @ykurilov/agent-chat-react@0.51.0
53
+
54
+ ## 0.50.0
55
+
56
+ ### Patch Changes
57
+
58
+ - @ykurilov/agent-chat-core@0.50.0
59
+ - @ykurilov/agent-chat-react@0.50.0
60
+
61
+ ## 0.49.0
62
+
63
+ ### Minor Changes
64
+
65
+ - 084cef7: **Ломает приложения (типы и своё хранилище сценариев):** у типа поля сценария `FlowFieldType` появилось значение `"boolean"`, у источника
66
+ расхода `UsageSource` — `"first_message"`, у `SteerResult.status` — `"held"`. Исчерпывающий `switch` по ним перестанет
67
+ собираться. Своё хранилище сценариев (`FlowStore`) должно реализовать новый `renewLease()` и третий аргумент `setDue`.
68
+
69
+ Сценарии для приложений: программа по дням (ночь, касания, рамки дня, «срок вышел»), своя служба чата и доставка с
70
+ исходами, пауза сотрудника, конец разговора с итогом, карточка и пояс человека в базе приложения, попутные задачи.
71
+ Проверка готового ответа: «чужие слова» и «выдуманные ссылки». Подробно — `docs/flows.md`, `docs/reply-check.md`,
72
+ раздел 0.49.0 в `CHANGES.md`.
73
+
74
+ - **flows**: формат сценария версии 2 (`schedule`, `nudges`, таймер `state_days`, поле `boolean`, `sideTasks`,
75
+ `afterEnd`; версия 1 читается сама); `attach({ portFor, deliver })`, `admit`, `pause`/`resume`/`noteStaffReply`,
76
+ `reportDelivery`, `onFinish`, `clientCard`, `people`, `settled`, `now`; причина перехода `reason`; перенос чата
77
+ через `loadFlow` (`stateId`, `startedAt`, `enteredAt`); заголовки судьи `judge.openrouter.headers`; встроенная задача
78
+ «узнать город»; `checkFlow(flow, catalog, { checks })` и `FlowAppChecks` из `/format`; в редакторе панели дней,
79
+ касаний, рамок и попутных задач, `catalog.fields`, кнопка «К началу следующего дня» (`advanceToNextDay`). Новые
80
+ таблицы `flow_people`, `flow_side_tasks` и колонки `flow_chats` создаются сами.
81
+ - **core**: `replyChecks` (список или `{ checks, web: "replace" | "hold" }`), `now`, `markUndelivered`, ответы
82
+ `beforeTurn` `{ hold: true }` и `{ reply: { text } }`, `deliverNotices({ timeZone, awaitAnswer, usageSource })`,
83
+ источник расхода `first_message`.
84
+ - **flows, надёжность**: обход таймеров до 5 ходов одновременно; копия продлевает аренду чата перед ходом и во время
85
+ него. В `FlowStore` новые `renewLease()` и `setDue(…, { unlessDelivery })`. Недоставленное касание (`failed`,
86
+ `window_closed`) не засчитывается — тот же шаг через свой срок от отказа; в `FlowRun` новое поле `touch`. «Окно
87
+ закрыто» снимает любое сообщение человека; «не завершил» считает тишину и от конца паузы сотрудника; пока ждёт
88
+ повтор доставки, срок не затирается и новых ходов нет.
89
+ - **server**: `createChatServer` передаёт службе `replyChecks` и `now`.
90
+ - **messengers**: `onAnswerDelivery` — исход доставки каждого ответа; закрытое окно ВК (950) —
91
+ `MessengerWindowClosedError` и исход `window_closed`; с `replyChecks` уходит только проверенный текст.
92
+ - **web-tools**: `createPersonLinkTool` — инструмент `person_link_view`, агент смотрит ссылку, которую прислал человек
93
+ (адрес из почты ссылкой не считается).
94
+ - **reply-check** (новый пакет): `foreignWords` и `unknownLinks`; ответ из одной выдуманной ссылки заменяется запасной
95
+ фразой `labels.linkOnlyReply` (`russianReplyCheckLabels`, `englishReplyCheckLabels`, `ReplyCheckLabels`).
96
+ - **react**: договор скина версии 24 — необязательное `undelivered` у ответа агента и надпись `answerUndelivered`
97
+ («Не доставлено»). Скин версии 23 работает без правок.
98
+
99
+ ### Как обновить приложение
100
+
101
+ 1. Поднимите все пакеты `@ykurilov/agent-chat-*` до этой версии вместе.
102
+ 2. Найдите в коде приложения исчерпывающие `switch` и словари по этим типам и добавьте ветки:
103
+ - по `FlowFieldType` (поля карточки сценария) — `"boolean"`, «да/нет», значения `true`/`false`;
104
+ - по `UsageSource` (отчёты расходов) — `"first_message"`, «агент написал первым»;
105
+ - по `SteerResult.status` — `"held"`, «сообщение записано, хода нет».
106
+ 3. Своё хранилище сценариев (свой `FlowStore`) — добавьте `renewLease({ chatId, owner, now, leaseMs }): Promise<boolean>`
107
+ (продлить аренду до `now + leaseMs`; `false` — чат уже не ваш) и третий аргумент `setDue(chatId, due, { unlessDelivery })`
108
+ (при `unlessDelivery` не писать срок, пока чат ждёт повтора доставки). Готовые хранилища SDK это уже умеют.
109
+ 4. Сценарии сохраняются версией 2, а 0.48 её не откроет. Перед откатом на 0.48 держите копии сценариев версии 1.
110
+ 5. Новое по желанию: каналы SDK и сценарий — `createMessengers({ onAnswerDelivery: (report) => flow.reportDelivery(report) })`;
111
+ проверка ответа — `pnpm add @ykurilov/agent-chat-reply-check` и `replyChecks` в `createChatServer`.
112
+ 6. Проверка: `pnpm typecheck` проходит, чат отвечает, сценарий версии 1 открывается в редакторе.
113
+
114
+ ### Patch Changes
115
+
116
+ - Updated dependencies [084cef7]
117
+ - @ykurilov/agent-chat-core@0.49.0
118
+ - @ykurilov/agent-chat-react@0.49.0
119
+
120
+ ## 0.48.0
121
+
122
+ ### Patch Changes
123
+
124
+ - fa10d5a: Сценарии поведения агента: разговор идёт по шагам с условиями выхода, судьёй, таймерами «написать первым» и карточкой
125
+ клиента, плюс редактор сценария с тестовым чатом. Подробно — `docs/flows.md`.
126
+
127
+ - **flows** (новый пакет): `createAgentFlow`, формат и проверка сценария, судья decision-1 с запасной моделью, таймеры,
128
+ карточка клиента, перевод чатов на новую версию, хранилища SQLite и PostgreSQL, `FlowEditor`, `FlowTestChat`, `ClientCardPanel`.
129
+ - **core**: новые точки для расширений — `agent.prepareTurn` (контекст хода с `resumed`, `audience`, `staff?`),
130
+ служебное сообщение `app`, готовый ответ `deliverNotices({ reply })`, `askModel` и `recordUsage`, источник расхода
131
+ `judge`, `ChatService.assertWithinLimit(ownerId, chatId)`; `chatInstructions(chat, turn?)` — второй аргумент с
132
+ `audience` (при сжатии истории его нет).
133
+ - **server**: `chatInstructions` в настройках принимает тот же второй аргумент `turn`.
134
+ - **messengers**: ответ сервера без модели (точный текст по таймеру сценария) теперь доходит до мессенджера.
135
+ - **skin-tandem**: новые входы `/flows` (`tandemFlowEditorSkin`) и `/client-card` (`tandemClientCardSkin`).
136
+
137
+ ### Как обновить приложение
138
+
139
+ Сценарии не нужны — обновление ничего не требует. Нужны — шаги в разделе «Как обновить приложение» в `CHANGES.md`:
140
+ `pnpm add @ykurilov/agent-chat-flows`, `createAgentFlow` + `flow.withChat(…)`, `flow.attach(service)`, `flow.start()`,
141
+ `OPENROUTER_API_KEY` на сервере. Тестовому чату нужен `readRun(chatId)` в `FlowTestChatConnection`; его адреса
142
+ закройте правами редактора. `firstMessages: false` теперь значит «без тихих часов и потолка», запрет —
143
+ `canWriteNow: () => false`. Проверка: `pnpm typecheck`, новый чат отвечает по первому состоянию сценария.
144
+ - Updated dependencies [fa10d5a]
145
+ - @ykurilov/agent-chat-core@0.48.0
146
+ - @ykurilov/agent-chat-react@0.48.0
147
+
148
+ ## 0.47.0
149
+
150
+ ### Patch Changes
151
+
152
+ - Updated dependencies [5a59c33]
153
+ - @ykurilov/agent-chat-core@0.47.0
154
+ - @ykurilov/agent-chat-react@0.47.0
155
+
156
+ ## 0.46.2
157
+
158
+ ### Patch Changes
159
+
160
+ - Updated dependencies [3d0d2f2]
161
+ - @ykurilov/agent-chat-core@0.46.2
162
+ - @ykurilov/agent-chat-react@0.46.2
163
+
164
+ ## 0.46.1
165
+
166
+ ### Patch Changes
167
+
168
+ - @ykurilov/agent-chat-core@0.46.1
169
+ - @ykurilov/agent-chat-react@0.46.1
170
+
171
+ ## 0.46.0
172
+
173
+ ### Patch Changes
174
+
175
+ - Updated dependencies [56d7528]
176
+ - Updated dependencies [a1933fd]
177
+ - Updated dependencies [52cc67d]
178
+ - @ykurilov/agent-chat-core@0.46.0
179
+ - @ykurilov/agent-chat-react@0.46.0
180
+
181
+ ## 0.45.0
182
+
183
+ ### Patch Changes
184
+
185
+ - Updated dependencies [eb74c58]
186
+ - Updated dependencies [ac9aede]
187
+ - @ykurilov/agent-chat-react@0.45.0
188
+ - @ykurilov/agent-chat-core@0.45.0
189
+
190
+ ## 0.44.0
191
+
192
+ ### Minor Changes
193
+
194
+ - 6a2b5d9: Живые демонстрации: блок ```html-demo в ответе запускается в изолированной рамке (`sandbox="allow-scripts"`, строгий CSP,
195
+ наружу только высота). `agent.liveDemos` на сервере — одна постоянная строка инструкции; `liveDemos` у `AgentChat`,
196
+ `ChatWorkspace` и `AgentPanel`. Договор скина v22: необязательные `AgentText.renderDemo` и `ChatSkin.demoColors`, Тандем
197
+ умеет. Мессенджеры вместо блока пишут `liveDemoOnSite`, если у службы чата `liveDemos` (новое необязательное поле `ChatService.liveDemos`).
198
+ Код живёт в рамке внутри рамки: уйти на чужой адрес он не может, WebRTC убран. Выключатель: в чате с чужим текстом (веб-инструменты, файл человека, инструмент с `metadata: { externalContent: true }`)
199
+ демонстрации не запускаются — признак `ChatHistory.externalContent` и кусок потока `data-external-content`. Вложенные рамки
200
+ и `<link rel=preconnect>` в документе демонстрации запрещены. Всё необязательное, ломающих изменений нет. Как обновить
201
+ приложение — в CHANGES.md, раздел 0.44.0.
202
+
203
+ ### Patch Changes
204
+
205
+ - Updated dependencies [6a2b5d9]
206
+ - @ykurilov/agent-chat-core@0.44.0
207
+ - @ykurilov/agent-chat-react@0.44.0
208
+
209
+ ## 0.43.0
210
+
211
+ ### Patch Changes
212
+
213
+ - Updated dependencies [763f4c5]
214
+ - @ykurilov/agent-chat-core@0.43.0
215
+ - @ykurilov/agent-chat-react@0.43.0
216
+
217
+ ## 0.42.0
218
+
219
+ ### Patch Changes
220
+
221
+ - Updated dependencies [a385276]
222
+ - @ykurilov/agent-chat-core@0.42.0
223
+ - @ykurilov/agent-chat-react@0.42.0
224
+
225
+ ## 0.41.0
226
+
227
+ ### Patch Changes
228
+
229
+ - Updated dependencies [9472fcb]
230
+ - @ykurilov/agent-chat-core@0.41.0
231
+ - @ykurilov/agent-chat-react@0.41.0
232
+
233
+ ## 0.40.0
234
+
235
+ ### Patch Changes
236
+
237
+ - Updated dependencies [0e7aab3]
238
+ - @ykurilov/agent-chat-core@0.40.0
239
+ - @ykurilov/agent-chat-react@0.40.0
240
+
241
+ ## 0.39.1
242
+
243
+ ### Patch Changes
244
+
245
+ - Updated dependencies [5c0f3af]
246
+ - @ykurilov/agent-chat-react@0.39.1
247
+ - @ykurilov/agent-chat-core@0.39.1
248
+
249
+ ## 0.39.0
250
+
251
+ ### Patch Changes
252
+
253
+ - Updated dependencies [3b90429]
254
+ - @ykurilov/agent-chat-core@0.39.0
255
+ - @ykurilov/agent-chat-react@0.39.0
256
+
257
+ ## 0.38.0
258
+
259
+ ### Patch Changes
260
+
261
+ - Updated dependencies [1cae504]
262
+ - Updated dependencies [4065a4a]
263
+ - @ykurilov/agent-chat-core@0.38.0
264
+ - @ykurilov/agent-chat-react@0.38.0
265
+
266
+ ## 0.37.0
267
+
268
+ ### Patch Changes
269
+
270
+ - Updated dependencies [7f49d12]
271
+ - Updated dependencies [ea78982]
272
+ - @ykurilov/agent-chat-core@0.37.0
273
+ - @ykurilov/agent-chat-react@0.37.0
274
+
275
+ ## 0.36.0
276
+
277
+ ### Patch Changes
278
+
279
+ - Updated dependencies [3487fb4]
280
+ - Updated dependencies [8c43b86]
281
+ - Updated dependencies [6f42418]
282
+ - Updated dependencies [5f13c74]
283
+ - @ykurilov/agent-chat-core@0.36.0
284
+ - @ykurilov/agent-chat-react@0.36.0
285
+
286
+ ## 0.35.0
287
+
288
+ ### Patch Changes
289
+
290
+ - Updated dependencies [6ceee78]
291
+ - Updated dependencies [2382026]
292
+ - @ykurilov/agent-chat-react@0.35.0
293
+ - @ykurilov/agent-chat-core@0.35.0
294
+
295
+ ## 0.34.0
296
+
297
+ ### Patch Changes
298
+
299
+ - Updated dependencies [d039ce4]
300
+ - @ykurilov/agent-chat-core@0.34.0
301
+ - @ykurilov/agent-chat-react@0.34.0
302
+
303
+ ## 0.33.0
304
+
305
+ ### Patch Changes
306
+
307
+ - Updated dependencies [4a72339]
308
+ - @ykurilov/agent-chat-core@0.33.0
309
+ - @ykurilov/agent-chat-react@0.33.0
310
+
311
+ ## 0.32.0
312
+
313
+ ### Minor Changes
314
+
315
+ - 2fa3104: Ломает приложения: файл или голосовое из мессенджера больше 20 МБ больше не принимается. Раньше такой файл
316
+ скачивался целиком в память и уходил во вложения чата.
317
+
318
+ Мессенджеры читают входящие данные с потолком размера. Тело вебхука Telegram, VK и MAX больше 1 МиБ не читается
319
+ целиком — ответ 413. Файл и голосовое скачиваются не больше 20 МБ: чтение обрывается на пределе, даже если мессенджер
320
+ не сообщил размер или занизил его. Человек получает фразу «файл слишком большой» (`fileTooBig`) или «голосовое
321
+ слишком длинное» (`voiceTooLong`), а не «не получилось».
322
+
323
+ ### Как обновить приложение
324
+
325
+ 1. Код менять не нужно. Если приложение принимало через мессенджер файлы больше 20 МБ, предупредите людей: такие файлы
326
+ теперь загружаются через веб-чат.
327
+ 2. Если приложение задаёт свои фразы бота (`texts`), проверьте `fileTooBig`: теперь она приходит и тогда, когда
328
+ мессенджер не сообщил размер файла.
329
+ 3. Проверка: файл до 20 МБ, отправленный боту, доходит до чата; файл больше — человек получает фразу `fileTooBig`.
330
+ - 853edb7: Ломает приложения: у `MessengerStore` три новых метода и новый аргумент `claimUpdate` — своё хранилище мессенджеров
331
+ перестанет собираться. Хранилищ из пакета это не касается.
332
+
333
+ Входящее сообщение из мессенджера больше не теряется навсегда.
334
+
335
+ - Раньше сервер, упавший между записью обновления и ответом, терял сообщение: повтор от мессенджера отбрасывался, а
336
+ недоделанное никто не подбирал. Теперь обновление лежит в хранилище с содержимым за копией сервера (аренда 2 минуты).
337
+ Копия упала — его подбирает живая копия или эта же после `start()`.
338
+ - База не приняла обновление — вебхук отвечает 503, и мессенджер повторяет. При опросе то же обновление пробуется
339
+ снова с растущей паузой (от секунды до 30 секунд), а не пропускается. Не записывается десять минут подряд —
340
+ пропускается с ошибкой в `logError`, чтобы одно битое обновление не держало бота.
341
+ - Postgres-хранилище убирает символ NUL из строк от мессенджера и модели: раньше запись с ним падала.
342
+ - Обработка сорвалась с ошибкой — повтор через полминуты. После трёх повторов или суток обновление брошено, ошибка
343
+ уходит в `logError`.
344
+ - Двойного ответа нет. Номер сообщения агенту берётся из ключа обновления. Повтор сверяется с историей чата: на
345
+ отвеченное сообщение второго хода нет. Записанное без ответа служба доводит сама, файлы не скачиваются снова.
346
+ - Приём — «хотя бы раз»: команда, обработанная перед падением сервера, может выполниться второй раз. Повтор встаёт
347
+ за более поздними сообщениями, порядок при повторе не гарантирован.
348
+ - Миграция `0003` для SQLite и Postgres добавляет колонки в `messenger_updates` и применяется сама.
349
+
350
+ ### Как обновить приложение
351
+
352
+ Хранилища пакета (память, SQLite, Postgres) и `ChatService` ядра — ничего делать не нужно, миграция применяется сама.
353
+
354
+ 1. Своё хранилище (поиск по `MessengerStore` в коде приложения): `claimUpdate` получает третий аргумент `work` и
355
+ сохраняет его содержимое; добавьте методы `renewUpdates`, `releaseUpdate`, `takeAbandonedUpdates`.
356
+
357
+ Было:
358
+
359
+ ```ts
360
+ claimUpdate(key: string, now: string): Promise<boolean>;
361
+ ```
362
+
363
+ Стало:
364
+
365
+ ```ts
366
+ claimUpdate(key: string, now: string, work?: UpdateWork): Promise<boolean>;
367
+ renewUpdates(keys: readonly string[], holder: string, leaseUntil: string): Promise<void>;
368
+ releaseUpdate(key: string, holder: string, retryAt: string): Promise<void>;
369
+ takeAbandonedUpdates(bot: { messenger: MessengerName; botId: string }, holder: string, now: string, leaseUntil: string, limit: number): Promise<AbandonedUpdate[]>;
370
+ ```
371
+
372
+ Типы `UpdateWork` и `AbandonedUpdate` — из `@ykurilov/agent-chat-messengers`. Образец — `createMemoryMessengerStore` пакета. `purgeBefore` не должен удалять необработанные обновления
373
+ с живой арендой (`lease_until` не раньше `before`); с арендой, истёкшей раньше `before`, — удаляет.
374
+ 2. Своя служба чата вместо `ChatService` ядра: `getHistory` отдаёт у сообщений номер (`id`). Без него повтор после
375
+ сбоя не узнает уже отправленное сообщение.
376
+ 3. Проверка: `pnpm typecheck` без ошибок; сообщение боту получает ответ.
377
+ - e7d6d48: Ломает приложения: аккаунт мессенджера, уже привязанный к другому пользователю, новым кодом больше не переносится,
378
+ а выдача и снятие привязки с чужой страницы получают 403.
379
+
380
+ Привязка аккаунтов мессенджеров стала строже.
381
+
382
+ - Аккаунт мессенджера, уже привязанный к одному пользователю, код другого пользователя больше не переносит. Человек
383
+ получает новую фразу `linkTaken`, привязка остаётся прежней, `onUnlinked` и `onLinked` не приходят. Раньше аккаунт
384
+ молча переезжал к владельцу кода. Чтобы перенести аккаунт, его сначала отключают в приложении у прежнего пользователя.
385
+ - Выдача и снятие привязки (`POST /links`, `DELETE /links/<мессенджер>`) с чужой страницы с куками человека получают
386
+ 403 `{ error: { kind: "forbidden_origin" } }`, как запросы чата. Новая настройка `allowedOrigins` у `createMessengers`
387
+ пускает нужные чужие страницы.
388
+ - Битый адрес отвязки (`/links/%`) — 400 вместо сбоя; тело `POST /links` больше 1 МиБ — 413.
389
+
390
+ ### Как обновить приложение
391
+
392
+ 1. Если панель «Подключить мессенджер» открыта на другом адресе, чем API (фронт на `https://app.example.com`, API на
393
+ `https://api.example.com`, или чат встроен в GetCourse), передайте тот же список, что у `createChatServer`:
394
+
395
+ ```ts
396
+ createMessengers({ ...options, allowedOrigins: ["https://app.example.com"] });
397
+ ```
398
+
399
+ Своя страница на том же адресе работает без настройки.
400
+ 2. Если приложение задаёт свои фразы бота (`texts`), можно задать и `linkTaken`. Не задана — фраза по умолчанию.
401
+ 3. Если приложение рассчитывало на перенос аккаунта мессенджера к другому пользователю по новому коду, теперь
402
+ человек сначала отключает аккаунт у прежнего пользователя.
403
+ 4. Проверка: в приложении «Подключить» выдаёт ссылку, привязка по ней проходит.
404
+ - dac242f: Ломает приложения: бот VK на Callback API без секретного ключа больше не запускается — `vkBot` бросает понятную
405
+ ошибку при старте, если `receive.callback.secret` пустой или не задан.
406
+
407
+ Раньше с пустым секретом настоящие события VK отклонялись (бот молчал), а подделка с `"secret": ""` проходила как
408
+ настоящая. Подтверждение адреса (`confirmation`) теперь тоже всегда сверяет секрет. Бот на Long Poll секрет не требует.
409
+
410
+ ### Как обновить приложение
411
+
412
+ Касается только ботов VK с `receive: { callback: … }`. Бот на Long Poll (`receive: { longPoll: true }`) менять не нужно.
413
+
414
+ 1. В сообществе VK: «Управление» → «Работа с API» → «Callback API» → «Настройки сервера» — впишите «Секретный ключ»
415
+ (латинские буквы и цифры) и сохраните.
416
+ 2. В файле, где создаётся бот (поиск по `vkBot(`), передайте тот же ключ, например из переменной окружения
417
+ `VK_CALLBACK_SECRET`:
418
+
419
+ Было:
420
+
421
+ ```ts
422
+ vkBot({ token, groupId, groupScreenName, receive: { callback: { secret: "", confirmation } } });
423
+ ```
424
+
425
+ Стало:
426
+
427
+ ```ts
428
+ vkBot({ token, groupId, groupScreenName, receive: { callback: { secret: process.env.VK_CALLBACK_SECRET!, confirmation } } });
429
+ ```
430
+
431
+ 3. Проверка: `pnpm typecheck`, сервер стартует без ошибки «vk: для Callback API нужен секретный ключ», сообщение боту
432
+ в VK получает ответ.
433
+
434
+ ### Patch Changes
435
+
436
+ - 2f95dfa: Группы: имя участника из профиля мессенджера уходит модели одной строкой не длиннее 64 знаков, без переводов строк,
437
+ управляющих и невидимых знаков. Так в имя не спрятать «инструкции» для модели. Пустое имя — номер участника (`id…`).
438
+
439
+ Обновление ничего не требует.
440
+ - 294e1e8: Хранилище мессенджеров больше не растёт без конца. После `start()` записи старше 30 дней убираются при запуске и
441
+ потом раз в час: коды привязки, принятые обновления, кнопки под ответами, обращения участников групп. Необработанное входящее обновление остаётся, пока
442
+ его держат или ждут повтора; брошенное больше 30 дней назад (например, бот убран из настроек) убирается. Раньше
443
+ `purgeBefore` никто не вызывал. У разговоров появился индекс по чату агента (миграция `0002` для SQLite и Postgres
444
+ применяется сама при создании хранилища): поиск разговоров чата больше не просматривает всю таблицу.
445
+
446
+ Обновление ничего не требует.
447
+ - Updated dependencies [4087a22]
448
+ - Updated dependencies [71c32b7]
449
+ - Updated dependencies [cae13bf]
450
+ - Updated dependencies [a94bda9]
451
+ - Updated dependencies [4e44492]
452
+ - Updated dependencies [ac9b7d3]
453
+ - Updated dependencies [27f4b1e]
454
+ - @ykurilov/agent-chat-react@0.32.0
455
+ - @ykurilov/agent-chat-core@0.32.0
456
+
457
+ ## 0.31.2
458
+
459
+ ### Patch Changes
460
+
461
+ - 6177508: VK Callback API: запрос подтверждения адреса (`confirmation`) теперь сверяет секрет, как и остальные события.
462
+ Раньше строку подтверждения получал любой, кто знает номер сообщества. Если в `receive.callback.secret` секрет
463
+ задан, `confirmation` с чужим или пустым `secret` получает 403. Секрет не задан — поведение прежнее.
464
+ - @ykurilov/agent-chat-core@0.31.2
465
+ - @ykurilov/agent-chat-react@0.31.2
466
+
467
+ ## 0.31.1
468
+
469
+ ### Patch Changes
470
+
471
+ - 8c539eb: Фразы бота по умолчанию без пустого обещания «Пишите — отвечу»:
472
+
473
+ - после привязки (`linked`) — «Готово: аккаунт подключён.»;
474
+ - `/start` без кода (`hello`) — «Здравствуйте! Чем помочь?»;
475
+ - `/help` (`helpSingle`, `helpMany`) — «Задайте вопрос сообщением. …» и список команд.
476
+
477
+ ### Как обновить приложение
478
+
479
+ Ничего менять не нужно. Если ваши тесты сравнивают ответ бота с прежними фразами — сравнивайте с
480
+ `defaultMessengerTexts.linked` (и т. п.) или задайте свои фразы через `texts`. Прежние фразы вернуть можно так же:
481
+ `texts: { linked: "Готово: аккаунт подключён. Пишите — отвечу." }`.
482
+ - @ykurilov/agent-chat-core@0.31.1
483
+ - @ykurilov/agent-chat-react@0.31.1
484
+
485
+ ## 0.31.0
486
+
487
+ ### Minor Changes
488
+
489
+ - 7f10a41: Бот только для привязки и уведомлений теперь собирается без обходных путей.
490
+
491
+ - **`service` необязателен.** Без службы чата бот привязывает и отвязывает аккаунты, а привязанному человеку на любое
492
+ сообщение, команду, голосовое, файл и кнопку отвечает новой фразой `notifyOnly` (пустая — тишина). Ход агента не
493
+ начинается, файлы и голосовые не скачиваются, нажатие кнопки подтверждается без текста. Отказ `resolveAccount`
494
+ по-прежнему главнее. Фразы по умолчанию не обещают разговор: `linked` — «Готово: аккаунт подключён.», `howToConnect`
495
+ зовёт подключить аккаунт ради уведомлений (`defaultMessengerTextsWithoutChat`). Порядок: фраза бота → общая `texts` →
496
+ умолчание бота без чата. Настройки разговора (`groups`, `conversations`, `chatForConversation`, `attachments`,
497
+ `transcribe`, `voiceReplies`, `taskReadyPing`, `webChatUrl`) без `service` задать нельзя: это ошибка типов. Типы
498
+ настроек: `MessengersOptions = MessengersBaseOptions & (MessengersChatOptions | MessengersWithoutChatOptions)`.
499
+ - **Меню команд по желанию.** `commandsMenu: false` в `createMessengers` или у бота (`telegramBot`, `maxBot`; бот
500
+ главнее) — `start()` не ставит меню и снимает поставленное раньше. Без `service` меню снимается всегда. Пустой список
501
+ в `adapter.setCommands([], scope)` теперь значит «снять меню»: Telegram зовёт `deleteMyCommands`, MAX шлёт пустой
502
+ список `commands` (так велит документация dev.max.ru; живым ботом снятие меню MAX не проверено). По умолчанию меню ставится, как раньше.
503
+ - **`onUnlinked(link)`.** Сигнал после того, как привязка удалена: человек отвязал аккаунт в приложении
504
+ (`DELETE /links/<мессенджер>`, по сигналу на каждый аккаунт) или тот же аккаунт мессенджера привязал другой
505
+ пользователь (сигнал о прежней привязке — перед `onLinked` новой). Сбой уходит в `logError` и отвязку не отменяет.
506
+ Сигнал может повториться — обработчик должен быть идемпотентным. Ответ 204 на отвязку ждёт обработчик: долгий
507
+ `onUnlinked` задерживает ответ.
508
+ - **`close()` у хранилищ обязателен в типе.** `createPostgresMessengerStore` и `createSqliteMessengerStore` возвращают
509
+ `ClosableMessengerStore`: `close()` закрывает свой пул или файл, повторный вызов ничего не делает. Не удалось
510
+ подготовить хранилище — соединение закрывается до ошибки.
511
+
512
+ ```ts
513
+ const store = await createPostgresMessengerStore({ connectionString });
514
+ const messengers = createMessengers({
515
+ store,
516
+ bots: [vkBot({ /* … */ texts: { linked: "Аккаунт привязан.", linkFailed: "", howToConnect: "", notifyOnly: "", writeInPrivate: "" } })],
517
+ basePath: "/api/messengers",
518
+ resolveUser,
519
+ onUnlinked: (link) => newsletter.unsubscribe(link.userId, `${link.messenger}:${link.externalUserId}`),
520
+ });
521
+ await messengers.start();
522
+ // при остановке: await messengers.stop(); await store.close();
523
+ ```
524
+
525
+ ### Как обновить приложение
526
+
527
+ 1. Если ничего не меняете — шагов нет: с `service` бот работает как в 0.30.0, меню ставится как раньше.
528
+ 2. Бот только для привязки, которому подсовывали пустую службу чата: уберите `service` и заглушку. Привязанным теперь
529
+ отвечает `notifyOnly` — задайте свою фразу в `texts` (или `""`). `linked` и `howToConnect` по умолчанию сменились
530
+ на фразы без обещания разговора; свои фразы из `texts` главнее. Если `resolveAccount` был нужен только для отказа
531
+ всем, его можно убрать: непривязанным отвечает `howToConnect`, привязанным — `notifyOnly`.
532
+ 3. Не нужно меню команд — `commandsMenu: false`. Старое меню снимется при следующем `start()`.
533
+ 4. Убираете человека из рассылки при отвязке — `onUnlinked`, идемпотентно («убрать, если есть»). Тяжёлую работу
534
+ ставьте в очередь: ответ 204 ждёт обработчик.
535
+ 5. При остановке сервера: `await messengers.stop(); await store.close();`. Вызов `store.close?.()` тоже работает.
536
+ 6. Свой адаптер мессенджера: `setCommands([], scope)` должен снимать меню (или ничего не делать, если меню нет).
537
+ 7. Тип службы брали из настроек (`MessengersOptions["service"]` или `Parameters<typeof createMessengers>[0]["service"]`)?
538
+ Теперь в нём есть и `undefined`, и вместе с `webChatUrl`, `groups` и другими настройками разговора `typecheck`
539
+ выдаст ошибку. Берите тип прямо: `import type { MessengerChatService } from "@ykurilov/agent-chat-messengers"`.
540
+ То же, если служба в переменной с типом «служба или `undefined`»: настройки разговора задаются только вместе с
541
+ настоящей службой.
542
+ 8. Проверка: `pnpm typecheck` проходит; после `start()` у бота без меню в Telegram нет `/help` и `/stop`. Снимаются
543
+ меню личных и групповых чатов; общее меню из BotFather (и меню для отдельных языков) очистите там. Снятие меню MAX
544
+ проверено только по документации MAX — посмотрите на живом боте.
545
+
546
+ ### Patch Changes
547
+
548
+ - @ykurilov/agent-chat-core@0.31.0
549
+ - @ykurilov/agent-chat-react@0.31.0
550
+
551
+ ## 0.30.0
552
+
553
+ ### Minor Changes
554
+
555
+ - 2514722: Фразы бота теперь настраиваются в приложении. Новая настройка `texts` есть у `createMessengers` (для всех ботов) и у
556
+ каждого бота: `telegramBot`, `vkBot`, `maxBot`. Фраза бота главнее общей, общая главнее фразы по умолчанию. Все фразы по
557
+ умолчанию и их тип — `defaultMessengerTexts` и `MessengerTexts` из `@ykurilov/agent-chat-messengers`. Пустая строка или
558
+ фраза-функция, вернувшая `""`, значит «ничего не отправлять». Исключения:
559
+
560
+ - нажатие своей кнопки бот всё равно подтверждает, только без текста, — иначе у человека вечно крутится загрузка. Без
561
+ ответа остаются только чужие и неизвестные кнопки, кнопки от того, кому писать нельзя, и кнопки в группе не от
562
+ плательщика;
563
+ - пустые `done`, `stopped`, `answerFailed` на месте строки статуса («Работаю…») удаляют это сообщение. Для этого у
564
+ адаптера появился необязательный метод `deleteMessage(sent)`; в Telegram, VK и MAX он есть. Свой адаптер без него
565
+ оставит строку статуса как есть;
566
+ - фразы над кнопками (`chatsTitle`, `approvalQuestion`, `taskApprovalQuestion`) заглушить нельзя: пустая — фраза по
567
+ умолчанию.
568
+
569
+ ```ts
570
+ createMessengers({
571
+ // …
572
+ texts: { howToConnect: "Это бот уведомлений. Откройте приложение." },
573
+ bots: [vkBot({ /* … */ texts: { linked: "Аккаунт привязан.", linkFailed: "", writeInPrivate: "" } })],
574
+ });
575
+ ```
576
+
577
+ `/stop` в личке теперь подчиняется тем же правилам, что обычное сообщение. Если `resolveAccount` отказал, человек
578
+ получает текст отказа, а пустой отказ — тишина. Раньше `/stop` от непривязанного шёл мимо `resolveAccount` и всегда
579
+ получал «Чтобы я отвечал, подключите этот аккаунт…». Рецепт «бот только для привязки и уведомлений» — в README пакета.
580
+
581
+ ### Как обновить приложение
582
+
583
+ 1. Если ничего не меняете — шагов нет: без `texts` бот говорит те же фразы, что в 0.29.0.
584
+ 2. Если в приложении есть `resolveAccount` с отказом (`{ refuse: "…" }`): теперь `/stop` от отказанного человека
585
+ получает этот же текст, а не «подключите этот аккаунт». Если нужен прежний текст, верните его в отказе.
586
+ 3. Нужны свои фразы — передайте `texts` в `createMessengers` или в нужного бота. Список фраз —
587
+ `defaultMessengerTexts`; тип `Partial<MessengerTexts>` подскажет имена.
588
+ 4. Проверка: `pnpm typecheck` проходит; непривязанный аккаунт пишет боту и получает вашу фразу (или ничего при `""`).
589
+
590
+ ### Patch Changes
591
+
592
+ - @ykurilov/agent-chat-core@0.30.0
593
+ - @ykurilov/agent-chat-react@0.30.0
594
+
595
+ ## 0.29.0
596
+
597
+ ### Minor Changes
598
+
599
+ - 4279ae0: Приложение теперь узнаёт, что человек привязал мессенджер: в `createMessengers` появилась необязательная настройка
600
+ `onLinked(link)`. Её вызывают после того, как привязка сохранена, и ждут до ответа «Готово: аккаунт подключён». Так
601
+ приложение может, например, выдать бонус за привязку ВК или Telegram. Неверный, просроченный и уже использованный код
602
+ сигнала не дают. Ошибка внутри `onLinked` уходит в `logError`, привязка не отменяется, человек получает обычный ответ.
603
+
604
+ ```ts
605
+ createMessengers({
606
+ // …
607
+ onLinked: async (link) => {
608
+ // link: { messenger, botId, externalUserId, userId, name, username?, linkedAt }
609
+ await grantLimitReset(link.userId, `${link.messenger}:${link.externalUserId}`);
610
+ },
611
+ });
612
+ ```
613
+
614
+ Сигнал может прийти не один раз для одного аккаунта мессенджера: человек отвязал и привязал снова, или тот же аккаунт
615
+ привязал другой пользователь приложения. Награду выдавайте идемпотентно, например по паре `messenger` + `externalUserId`.
616
+
617
+ Обновление ничего не требует: без `onLinked` всё работает как раньше. Подробно — README пакета и `docs/messengers.md`,
618
+ раздел «Сигнал о привязке».
619
+
620
+ ### Patch Changes
621
+
622
+ - @ykurilov/agent-chat-core@0.29.0
623
+ - @ykurilov/agent-chat-react@0.29.0
624
+
625
+ ## 0.28.0
626
+
627
+ ### Patch Changes
628
+
629
+ - Updated dependencies [11c3c97]
630
+ - Updated dependencies [d63ccaa]
631
+ - Updated dependencies [6a2f903]
632
+ - Updated dependencies [189cba9]
633
+ - @ykurilov/agent-chat-core@0.28.0
634
+ - @ykurilov/agent-chat-react@0.28.0
635
+
636
+ ## 0.27.0
637
+
638
+ ### Minor Changes
639
+
640
+ - 5e4ed28: **Что может сломаться:**
641
+
642
+ - исчерпывающий `switch` по виду ошибки сервера (`HandlerErrorKind`) и виду отказа на экране (`AppRefusalKind`): новые значения `action_forbidden`, `chat_closed`, `chat_creation_refused`;
643
+ - исчерпывающий `switch` по причине `turnNotStarted` (`TurnNotStartedReason`): новое значение `chat_closed`;
644
+ - свой словарь надписей `ChatLabels`, собранный целиком: новое обязательное поле `chatListClosed`.
645
+
646
+ Права человека в чате и закрытый чат. Приложение функцией `chatCapabilities` решает, что можно человеку: правка, «Переделать», «Повторить», ветки, выбор модели, правка ответа, список чатов, сжатие, файлы, голос. Чат можно закрыть из инструмента (`model.closeChat`) или из кода (`service.closeChat`, `reopenChat`): переписка видна, писать дальше нельзя. Хук `beforeCreateChat` решает, можно ли человеку завести новый чат. Без новых настроек всё работает как раньше. Подробно — [docs/in-product-agent.md](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/in-product-agent.md), разделы 9, 10 и 11.
647
+
648
+ - Права: опечатка в названии права (`edits` вместо `edit`) — ошибка приложения (`TypeError`), а не молчаливый доступ. За один вызов службы права спрашиваются не больше раза.
649
+ - Закрытие побеждает паузу: если чат закрыт, пока ответ ждёт подтверждения или виджета, ждущие запросы закрываются отказом, как при «Стопе», и ответ считается законченным.
650
+ - Закрытие из инструмента записывается сразу при вызове: после падения сервера чат уже закрыт. Чат, закрытый во время проверки перед ходом, хода не начинает: 409 `chat_closed`, резерв приложения закрывается `turnNotStarted` с причиной `chat_closed`.
651
+ - Новый чат: `beforeCreateChat({ userId, origin? })` вернул `{ code, message }` — `POST {base}` отвечает `403 { error: { kind: "chat_creation_refused", code, message } }`, мессенджер (`/new`, первый чат) — текстом. Чаты объектов и импорт хук не спрашивают.
652
+ - `listChats` отдаёт `closed`: на экране и в `/chats` мессенджера у закрытого чата пометка.
653
+ - Сервер: запрещённое действие — `403 { error: { kind: "action_forbidden", action, code, message } }`, закрытый чат — `409 { error: { kind: "chat_closed", code, message } }`. Мессенджеры отвечают человеку текстом.
654
+ - Мессенджеры: голосовое при `voice: false` и файлы при `attachments: false` не скачиваются и не распознаются, `/chats` смотрит на `chatList`. Своя служба может реализовать необязательный `assertCapability`.
655
+ - Песочница: закрытие работает как в бою, а кнопка «Открыть чат снова» (`POST {sandboxBase}/chats/:chatId/reopen`, только админ) позволяет продолжить тест. Скину песочницы — необязательный `SandboxSkin.ChatReopen`, надписи `SandboxLabels.chatReopen` (необязательные); в Тандеме кнопка своя.
656
+ - Экран: кнопки по правам прячутся сами, после ответа 403 `action_forbidden` права перечитываются, закрытый чат блокирует поле и показывает текст закрытия. Голосовое сообщение при `voice: false` не уходит. Новое:
657
+ - `AppRefusalKind` — `chat_creation_refused`; `useChatList` — `onCreateRefused` и `creationRefusal`, `ChatWorkspace` отдаёт отказ в `onTurnRefused`;
658
+ - `ChatListEntry.closed` и `ChatListItemView.closed` — пометка закрытого чата в списке, надпись `chatListClosed` («Закрыт», обязательное поле `ChatLabels`);
659
+ - `reloadKey` у `AgentChat`, `useAgentChat` и `ChatWorkspace` — перечитать историю, когда чат изменили не через экран;
660
+ - `onChatUnavailable` у `AgentChat` — служебный колбэк для `ChatWorkspace`, приложению не нужен;
661
+ - `ChatWorkspace` при `chatList: false` не мигает списком: он появляется после первой истории (и при `chatList: true` — тоже с этой короткой задержкой) и потом не запрашивается;
662
+ - `onTurnRefused` получает новые виды отказов, `onChatAccessChange` сообщает права и закрытие, `ChatWorkspace` при `chatList: false` показывает только чат.
663
+ - Скин: договор v20 — необязательный `ChatClosedNotice` и необязательный `list` у `ChatListSkin.ChatWorkspace` ([docs/skin-v20.md](https://github.com/ykurilov/agent-chat-sdk/blob/main/docs/skin-v20.md)). Скин v19 работает без правок. В Тандеме — `TandemChatClosedNotice`.
664
+ - Хранилища: колонка `closed` у чатов, миграция SQLite и PostgreSQL применяется сама. Своё хранилище может реализовать необязательный `setChatClosed(chatId, closed | null)` и отдавать `closed` в `getChat`; общий тест — `describeChatStorageContract(…, { chatClosure: "required" })`.
665
+
666
+ ### Как обновить приложение
667
+
668
+ 1. Найдите в коде приложения исчерпывающие `switch` по виду ошибки сервера чата, по `refusal.kind` из `onTurnRefused` и по `reason` в `turnNotStarted`. Искать: `kind: "turn_refused"`, `case "turn_refused"`, `case "access_refused"`, `HandlerErrorKind`, `AppRefusalKind`, `TurnNotStartedReason`, `case "busy"`.
669
+ 2. Добавьте ветки для новых видов:
670
+
671
+ Было:
672
+
673
+ ```ts
674
+ switch (refusal.kind) {
675
+ case "turn_refused": return showLessonClosed(refusal.message);
676
+ case "access_refused": return showNoAccess(refusal.message);
677
+ default: { const never: never = refusal.kind; return never; }
678
+ }
679
+ ```
680
+
681
+ Стало:
682
+
683
+ ```ts
684
+ switch (refusal.kind) {
685
+ case "turn_refused": return showLessonClosed(refusal.message);
686
+ case "access_refused": return showNoAccess(refusal.message);
687
+ case "chat_closed":
688
+ case "action_forbidden": return; // чат уже показал текст сам
689
+ case "chat_creation_refused": return showToast(refusal.message);
690
+ default: { const never: never = refusal.kind; return never; }
691
+ }
692
+ ```
693
+
694
+ В `switch` по `reason` в `turnNotStarted` добавьте `case "chat_closed":` рядом с `case "busy":`: резерв снимается так же.
695
+
696
+ 3. Свой словарь `ChatLabels`, собранный целиком (не поверх `russianChatLabels`): добавьте `chatListClosed: "Закрыт"`.
697
+ 4. Своё хранилище чата (не SQLite и не PostgreSQL из SDK): если нужен закрытый чат, реализуйте `setChatClosed` и отдавайте `closed` в `getChat`. Без этого `closeChat` бросает ошибку, а инструменты не получают `model.closeChat`.
698
+ 5. Проверка: `pnpm typecheck` проходит, чат открывается и отвечает.
699
+
700
+ Заметка к 0.28: закрытие станет записью хода, и правка, «Переделать» и ветки в закрытом чате станут возможны. Чтобы сохранить нынешнее поведение, задайте права: `chatCapabilities: ({ chat }) => chat.closed ? { edit: false, regenerate: false, branches: false } : {}`.
701
+ - e0bb519: Маршруты OpenRouter и переход на запасного провайдера посреди ответа.
702
+
703
+ - **Маршруты OpenRouter.** `createOpenRouterProvider({ id, displayName, only, order, allowFallbacks, requireParameters })`.
704
+ Маршрут с `id` — отдельный провайдер каталога: например, `or-deepseek` только с хозяином `deepseek` и
705
+ `or-fireworks` только с `fireworks`. Он обслуживает те же модели, что `openrouter`: id моделей, цены, закреплённые
706
+ версии и отметка «видит картинку в результате инструмента» переходят сами. Своя запись модели для маршрута главнее.
707
+ Провайдер `openrouter` должен остаться в каталоге, иначе ошибка при сборке каталога. Маршруты попадают и в порядок
708
+ запасных провайдеров по умолчанию. С `order` сортировка `sort: "throughput"` по умолчанию больше не ставится; явный
709
+ `sort` остаётся. Один хозяин и в `only`, и в `ignore` — ошибка при создании; все хозяева `only` запрещены профилем
710
+ модели — понятная ошибка запроса.
711
+ - **Переход посреди ответа** (по желанию): `agent.providerFallback: { onSilence: "restart-step-on-next" }`. Модель
712
+ замолчала дольше `modelSilenceTimeout`, до первого куска или посреди ответа. Тогда шаг обрывается и начинается заново
713
+ у следующего провайдера списка `byModel`, без повтора у того же. Показанный текст шага стирается: клиент получает часть
714
+ `data-step-reset` («убрать всё от последней части `step-start`»). В сохранённом ответе и в запросе модели стёртого
715
+ нет. `useAgentChat` и мессенджеры убирают стёртое сами, и решения хука («Повторить», «Ответить заново») стёртого не
716
+ видят. Своя лента на сырых частях — `withoutResetSteps(parts)` из core или react. Ход с переходом сразу после `start`
717
+ присылает кусок `data-steps-may-reset` (только клиенту): мессенджер по нему держит части идущего шага в живом
718
+ сообщении до конца шага, а стёртый целиком текст заменяет фразой «Пишу ответ заново…».
719
+ Шаг, где инструмент уже получил вход целиком, не стирается: ответ продолжается у запасного.
720
+ `scope: "step"` — следующий шаг снова у основного провайдера (по умолчанию `"turn"`); только после перехода при
721
+ молчании, запасной по перегрузке остаётся до конца хода. Перегрузка у запасного ведёт к следующему за ним.
722
+ `maxSwitches` — переходов за ход, по умолчанию 1; каждый съедает шаг из предела шагов. Стёртый шаг оплачен и есть в
723
+ расходе хода и в записи после перезапуска сервера. Срок хода `turnTimeoutMs` идёт и во время перехода;
724
+ `maxOutputTokens` действует в каждом заходе; после начала `flush` новых заходов нет (`stopReason: "shutdown"`);
725
+ при «Стоп» или остановке службы перехода нет и показанное не стирается. В журнале сервера — строка
726
+ `[переход при молчании]`.
727
+ - **Остановка службы — для всех приложений.** С этой версии `flush` с первых мгновений ожидания идущих ответов
728
+ запрещает ходам новые заходы: к запасному провайдеру, автопродолжение, продолжение после сжатия. Ход, которому нужен новый
729
+ заход, кончается остановкой (`stopReason: "shutdown"`), даже без `onSilence`. Раньше такой ход мог начать новый
730
+ заход посреди остановки сервера.
731
+
732
+ ### Как обновить приложение
733
+
734
+ Ничего делать не нужно: без `onSilence` всё работает как раньше. Включить переход — `agent.providerFallback:
735
+ { byModel: { "deepseek-4.1-flash": ["or-fireworks"] }, onSilence: "restart-step-on-next" }` и маршруты в
736
+ `extendCatalog(..., { addProviders: [...] })`. Если лента строится на сохранённых или сырых частях без `useAgentChat` —
737
+ пропускайте их через `withoutResetSteps` (есть в core и в react). Если маршрут OpenRouter задаёт `order` и рассчитывал
738
+ на сортировку по скорости, задайте `sort: "throughput"` явно. Проверка: `pnpm typecheck`.
739
+
740
+ ### Patch Changes
741
+
742
+ - Updated dependencies [d8341b2]
743
+ - Updated dependencies [5e4ed28]
744
+ - Updated dependencies [e0bb519]
745
+ - @ykurilov/agent-chat-core@0.27.0
746
+ - @ykurilov/agent-chat-react@0.27.0
747
+
748
+ ## 0.26.2
749
+
750
+ ### Patch Changes
751
+
752
+ - Updated dependencies [f24858a]
753
+ - @ykurilov/agent-chat-core@0.26.2
754
+ - @ykurilov/agent-chat-react@0.26.2
755
+
756
+ ## 0.26.1
757
+
758
+ ### Patch Changes
759
+
760
+ - Updated dependencies [5b52515]
761
+ - @ykurilov/agent-chat-react@0.26.1
762
+ - @ykurilov/agent-chat-core@0.26.1
763
+
764
+ ## 0.26.0
765
+
766
+ ### Patch Changes
767
+
768
+ - Updated dependencies [eb43e47]
769
+ - @ykurilov/agent-chat-core@0.26.0
770
+ - @ykurilov/agent-chat-react@0.26.0
771
+
772
+ ## 0.25.0
773
+
774
+ ### Patch Changes
775
+
776
+ - Updated dependencies [99d426e]
777
+ - Updated dependencies [d8201f6]
778
+ - @ykurilov/agent-chat-core@0.25.0
779
+ - @ykurilov/agent-chat-react@0.25.0
780
+
781
+ ## 0.24.0
782
+
783
+ ### Minor Changes
784
+
785
+ - 9b895cd: Ломает приложения: только исчерпывающие `switch` по видам ошибок сервера и событиям чата (новые значения) и код, который
786
+ сам читает ответ `resolveUser`. Без новых настроек поведение прежнее.
787
+
788
+ Агент внутри продукта: агент работает с объектами приложения — уроком, заказом, документом. Всё по желанию; без новых
789
+ настроек запрос к модели не меняется ни на байт. Подробно — `docs/in-product-agent.md`.
790
+
791
+ - **Проверка перед ходом.** `beforeTurn({ userId, chat, action, text? })` отказывает своими словами: 403 `turn_refused`
792
+ до любых записей и лимитов, на отправке, правке, повторе, переделке, подтверждении, виджете, служебных сообщениях, фоновой
793
+ задаче и в мессенджерах. `resolveUser` может вернуть `{ refused: { code, message, status? } }` → `access_refused` на всех
794
+ адресах чата, голоса, голосового режима (WebSocket 4401) и мессенджеров. React: `onTurnRefused`, текст отказа вместо «нет
795
+ связи», сообщение возвращается в поле. Ручная проверка — `service.assertTurnAllowed`.
796
+ - **Итог хода.** `afterTurn(turn)` после каждого хода с повторами и сроком (`afterTurnAttempts`, `afterTurnTimeoutMs`),
797
+ переживает перезапуск сервера. `TurnResult`: владелец и автор хода, текст, части, расход, действие, канал, объект,
798
+ сотрудник. События приложения `{ type, data? }` — из хука, из инструмента (`model.emitAppEvent`) и вне хода
799
+ (`service.publishAppEvent`); в React `appEvents` и `onAppEvent`, события переживают перезагрузку. Модели не уходят.
800
+ - **Чат объекта.** Ключ и данные объекта у чата, один чат на ключ у человека: `service.openSubjectChat`, на сервере
801
+ `subjectChats` (`POST …/subject`), в React `<AgentChat subject="lesson:42" />`. Данные объекта видят `beforeTurn`,
802
+ инструменты (`model.subject`) и `afterTurn`; в браузер и модели не уходят.
803
+ - **Приветствие.** `greeting: { text }` — первое сообщение от агента без вызова модели. Проверено на всех моделях каталога.
804
+ - **Лимит сообщений.** Окно лимитов с `limitMessages` вместо `limitUsd`, `exemptUsers` — люди без лимитов,
805
+ `onMessageThreshold` — пороги. Отказ — 429 с `unit: "messages"`.
806
+ - **Импорт старой переписки.** `service.importChat({ ownerId, chat, messages })` с датами и картинками
807
+ (`attachments.importFile`), без хода, памяти, расхода и лимитов.
808
+ - **Сотрудник проверяет чат человека.** Песочница с опцией `people`: админ видит чаты человека только на чтение и
809
+ проверяет ответы в копии у себя. Память человека в копии только читается, расход — на админа, человек ничего не видит.
810
+ Инструменты приложения получают `model.staff` и в копии в данные человека не пишут.
811
+ - **Свои кнопки у поля ввода.** `composerActions` с помощником `{ value, setValue, send, addFiles, busy, blocked, canSend }`.
812
+ Договор скина v19: `Composer.actions`; скин v18 работает без кнопок, Тандем готов.
813
+ - **Хранилища.** Миграции применяются сами: SQLite `0015`, PostgreSQL `0014` (ключ объекта, состояние хука итога).
814
+
815
+ ### Что может сломаться
816
+
817
+ - **Исчерпывающие `switch` по закрытым спискам.** Новые значения: вид ошибки сервера `turn_refused` и `access_refused`
818
+ (`HandlerErrorKind`), события чата `after-turn-failed` и `app-event` (`ChatEvent`), `unit` у отказа лимита. Добавьте ветки
819
+ и надписи.
820
+ - **Список чатов без чатов объектов.** `listChats` и `GET {basePath}` по умолчанию не показывают чаты с ключом объекта.
821
+ Касается только приложений, которые начнут ими пользоваться; показать — `chatList: { subjectChats: true }` или
822
+ `listChats(userId, { subjects: "include" })`.
823
+ - **Свой `resolveUser` в обёртках.** Тип ответа — `ResolvedUser` (`{ id } | { refused } | null`). Код, который сам вызывает
824
+ `resolveUser` и читает `.id`, сначала проверяет `"refused" in user`.
825
+ - **Своё хранилище.** Для новых возможностей нужны необязательные методы (`findChatBySubject`, `importChat`,
826
+ `setAfterTurnState`, `listPendingAfterTurn`); без них эти возможности при включении дают ошибку при старте, остальное
827
+ работает. `listChats(ownerId, options?)` получил второй параметр — чаты с ключом скрывать по умолчанию.
828
+ - **Свой `ChatService`** (заглушки в тестах) добавляет новые методы: `openSubjectChat`, `importChat`, `assertTurnAllowed`, `peekHistory`,
829
+ `publishAppEvent`.
830
+ - **Своя реализация `SpendingLimits`** может добавить `takeMessage` — без него сообщения не считаются.
831
+ - **`AgentChat`: `chatId` стал необязательным** — нужен ровно один из `chatId` и `subject`; ни одного или оба — ошибка при
832
+ рендере. При смене `chatId` чат пересоздаётся: черновик и состояние прошлого чата не переносятся.
833
+ - **Окна лимитов: `limitUsd` стал необязательным** (у окна сообщений его нет). Код, который читает свои окна, проверяет
834
+ `limitMessages`.
835
+ - **Отказ сервера в React** (`ServerRefusal`) получил `kind` и `code`. 401 со словами приложения (`access_refused`) — это
836
+ отказ, а не «вход истёк».
837
+ - **«Пустой чат»** для «Нового чата»: чат объекта пустым не считается, обычный чат с одним приветствием — считается.
838
+ - Новый подпуть `@ykurilov/agent-chat-core/access` — помощники отказа в доступе для своих обработчиков.
839
+
840
+ ### Как обновить приложение
841
+
842
+ 1. `pnpm up "@ykurilov/agent-chat-*@^0.24.0"`. Миграции базы применяются сами при открытии хранилища.
843
+ 2. Исчерпывающие `switch` по `HandlerErrorKind` (сервер) и `ChatEvent` (`chatEvents` ядра): найдите их поиском и добавьте
844
+ ветки `turn_refused`, `access_refused`, `after-turn-failed`, `app-event`. Обязательных надписей не прибавилось: текст
845
+ отказа приходит с сервера. Свой словарь для лимита сообщений — необязательные `spendingMessagesLeft`,
846
+ `spendingMessagesExhausted`, `editMessageLimit`.
847
+
848
+ Было: `switch (event.type) { case "tasks-changed": … case "notice-added": … default: assertNever(event) }`
849
+ Стало: те же ветки плюс `case "after-turn-failed": case "app-event": break;`
850
+ 3. Свой код, который сам зовёт `resolveUser` и читает `.id`: сначала `if (user && "refused" in user) …`.
851
+ 4. Ничего больше не нужно, если новые возможности не включать. Чтобы включить — `docs/in-product-agent.md`.
852
+ 5. Свой скин: кнопки у поля появятся, когда `Composer` начнёт рисовать `actions` (договор v19, `docs/skin-v19.md`).
853
+ 6. Проверка: `pnpm typecheck` без ошибок, чат открывается и отвечает как раньше.
854
+
855
+ ### Patch Changes
856
+
857
+ - Updated dependencies [9b895cd]
858
+ - @ykurilov/agent-chat-core@0.24.0
859
+ - @ykurilov/agent-chat-react@0.24.0
860
+
861
+ ## 0.23.0
862
+
863
+ ### Patch Changes
864
+
865
+ - Updated dependencies [30fb729]
866
+ - @ykurilov/agent-chat-react@0.23.0
867
+ - @ykurilov/agent-chat-core@0.23.0
868
+
869
+ ## 0.22.0
870
+
871
+ ### Patch Changes
872
+
873
+ - @ykurilov/agent-chat-core@0.22.0
874
+ - @ykurilov/agent-chat-react@0.22.0
875
+
876
+ ## 0.21.0
877
+
878
+ ### Patch Changes
879
+
880
+ - Updated dependencies [7c3bcc4]
881
+ - @ykurilov/agent-chat-core@0.21.0
882
+ - @ykurilov/agent-chat-react@0.21.0
883
+
884
+ ## 0.20.0
885
+
886
+ ### Minor Changes
887
+
888
+ - fd16b01: Два новых пакета.
889
+
890
+ - `@ykurilov/agent-chat-background` — фоновые задачи: инструмент или помощник работает в фоне, итог приходит в чат служебным сообщением, задачи переживают перезапуск сервера. Хранилища: память, SQLite, PostgreSQL. README пакета и `docs/background.md`.
891
+ - `@ykurilov/agent-chat-messengers` — чат приложения в Telegram, VK и MAX: привязка аккаунтов, вебхук или опрос, группы, файлы, голос, голосовые ответы, карточки задач, доставка «хотя бы раз». README пакета и `docs/messengers.md`. Для вида панели подключения — `MessengersSkin` v1.
892
+
893
+ Что сделать в приложении: ставить нужно только если нужны фон или мессенджеры; подключение — по README пакетов.
894
+
895
+ ### Patch Changes
896
+
897
+ - Updated dependencies [fd16b01]
898
+ - Updated dependencies [fd16b01]
899
+ - @ykurilov/agent-chat-core@0.20.0
900
+ - @ykurilov/agent-chat-react@0.20.0