@ykurilov/agent-chat-core 0.80.0 → 0.81.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/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # @ykurilov/agent-chat-core
2
2
 
3
+ ## 0.81.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 58204d2: **Ломает приложения со своим хранилищем мессенджеров или сценариев.** Хранилища из пакетов (память, SQLite,
8
+ Postgres) уже готовы — если вы передаёте их, менять ничего не нужно.
9
+
10
+ - `MessengerStore`: новый обязательный метод `saveLinkIfFree` и признак `guarded: true`. Привязка аккаунта по коду
11
+ идёт только через `saveLinkIfFree`: два кода разных людей на один аккаунт мессенджера больше не переписывают друг
12
+ друга.
13
+ - `FlowStore`: новый обязательный метод `swapDelivery` и признак `guarded: true`.
14
+ - Без `guarded: true` `createMessengers` и `createAgentFlow` сразу бросают `TypeError`. Признак — обещание, что
15
+ хранилище выполняет условия держателя и аренды (список — в шаге 3 ниже).
16
+
17
+ Новое для своей доставки сценария (`attach({ deliver })`):
18
+
19
+ - Исход `{ status: "await_repair" }` — канал сломан и ждёт починки (например, отозван токен бота). Ответ ждёт,
20
+ попытка не тратится, новых ходов и касаний нет. Починили — `flow.resumeDelivery({ ownerId, chatId, messageId })`:
21
+ тот же `messageId`, текст и части уходят ближайшим обходом таймеров. Ответ: `resumed`, `pending`, `busy`,
22
+ `not_found` или `dropped` (человек уже написал после ответа — ответ снят и помечен недоставленным). Ожидание
23
+ переживает перезапуск. В `reportDelivery` этот исход — `TypeError`.
24
+ - Пока идёт `deliver`, аренда чата продлевается: вторая копия сервера ту же попытку не начинает. Копия упала посреди
25
+ отправки — та же попытка повторяется через её паузу. Итог устаревшей копии не пишется.
26
+ - Перед самым `deliver`, уже под резервом, SDK ещё раз сверяет: разговор не закончен после хода (`flow.end`), ответ
27
+ есть в ветке, после починки человек не написал после него. Иначе `deliver` не зовётся, ответ помечен недоставленным.
28
+ - Повтор доставки прощания у законченного разговора теперь доходит — без нового хода и без открытия разговора.
29
+ - «Ровно один раз» снаружи SDK не обещает: после падения копии тот же ответ может уйти ещё раз с тем же `messageId`.
30
+ Двойную отправку отсекает приложение по `messageId`.
31
+
32
+ Остальные исправления аудита работают без правок в приложении:
33
+
34
+ - Ход группы мессенджера остаётся групповым и тогда, когда агент пишет первым, когда человек правит сообщение
35
+ группы, шлёт поправку к ходу или нажимает кнопку-подсказку: без памяти и личных блоков владельца, только
36
+ разрешённые группе инструменты, с пределом сообщений участника.
37
+ - Удаление чата ждёт хода на любой копии сервера: идёт ход — `ChatBusyError`, чат цел.
38
+ - Не удалось отметить вложения сообщения прикреплёнными — ошибка доходит до вызывающего, сообщение не записывается,
39
+ место в счёте сообщений отдаётся назад; повтор того же сообщения проходит.
40
+ - Сценарий, который не сходится с подтверждениями приложения (`confirmations`, `exitGuards`), проверяется до записи
41
+ версии и закрепления чата: чат идёт без сценария, перевод чатов на такую версию — отказ.
42
+ - Копия сервера, у которой забрали аренду таймера сценария или обновления мессенджера, больше не шлёт касание и не
43
+ переписывает срок и итог другой копии.
44
+
45
+ ### Как обновить приложение
46
+
47
+ Нужно только если приложение передаёт в `createMessengers` или `createAgentFlow` своё хранилище (не
48
+ `createMemoryMessengerStore`, `createSqliteMessengerStore`, `createPostgresMessengerStore`, `memoryFlowStore`,
49
+ `sqliteFlowStore`, `postgresFlowStore`).
50
+
51
+ 1. Найдите свою реализацию `MessengerStore` (поиск по `MessengerStore` или `saveLink(`).
52
+ 2. Добавьте метод — одной атомарной операцией базы:
53
+
54
+ ```ts
55
+ async saveLinkIfFree(link) {
56
+ // Аккаунт свободен или уже за link.userId — записать (имя и время обновить) и вернуть "saved".
57
+ // Аккаунт за другим пользователем — ничего не менять и вернуть "taken".
58
+ }
59
+ ```
60
+
61
+ В SQL это `insert … on conflict (messenger, bot_id, external_user_id) do update set … where user_id = $userId
62
+ returning …`: строка вернулась — `"saved"`, нет — `"taken"`. Отдельные «найти, потом записать» не годятся: между
63
+ ними успевает второй код.
64
+ 3. Добавьте признак `guarded: true` в объект хранилища. Он обещает:
65
+ - `finishUpdate(key, status, now, holder)`, `renewUpdates` и `releaseUpdate` пишут, только пока обновление за этим
66
+ держателем; `acquireLease` не отдаёт чужую живую аренду;
67
+ - `saveLinkIfFree` — одна атомарная операция.
68
+ 4. Своё `FlowStore` (если есть): метод `swapDelivery(chatId, { expected, next, due, claim, freeAt, endedAt })` одной
69
+ транзакцией. Заменить запись доставки, только если сейчас там `expected` (совпали `messageId`, `attempt` и `token`;
70
+ `null` — записи нет). С `claim` — только если аренда чата свободна, уже у `claim.owner` или у `claim.from`, и взять
71
+ её до `now + leaseMs`. С `freeAt` — только если живой аренды нет. С `endedAt` — только если конец разговора всё
72
+ ещё этот (`null` — не закончен). `due` записать вместе с записью. Вернуть `true`,
73
+ если заменил этот вызов. Плюс `guarded: true`, который обещает:
74
+ - `setDue(…, { leaseOwner })` пишет, только пока аренда у этой копии; `{ unlessDelivery: true }` — только если
75
+ доставки не ждёт;
76
+ - `leaseDue` не берёт живую аренду, а законченные разговоры берёт только со сроком `FLOW_DELIVERY_EXIT`;
77
+ - `endChat` при ждущей доставке не снимает срок и аренду.
78
+ Образец — `postgresFlowStore` и `sqliteFlowStore` в `@ykurilov/agent-chat-flows`.
79
+ 5. Проверка: `pnpm typecheck` проходит. Без признака или без `saveLinkIfFree` запуск сразу бросает `TypeError` с
80
+ подсказкой, без `swapDelivery` — `attach({ deliver })`.
81
+
82
+ ## 0.80.1
83
+
84
+ ### Patch Changes
85
+
86
+ - d59ae4e: После сжатия истории в режиме `contextBlocksRefresh: "history-start"` полный свежий снимок приложения размещается рядом со сводкой. Старые снимки оставленного хвоста больше не возвращают устаревшие данные; последующие изменения идут от свежей базы. Ошибка источника сохраняет прежний fallback, успешный пустой снимок тоже учитывается. Дата, память, место и notices не переносятся; сохранённые ответы не переписываются.
87
+
88
+ Запрос сжатия использует тот же сборщик frozen context, что и обычный ход, с исключением личных app-блоков, памяти, даты и места из сохраняемой сводки. Задание пересказа уточняет границу сжимаемой части. Настройки рассуждений провайдера не меняются. Обновить все пакеты одной версией; SQL-миграций и новых настроек нет.
89
+
3
90
  ## 0.80.0
4
91
 
5
92
  ### Minor Changes
@@ -1700,9 +1700,13 @@ function channelOfMessage(message) {
1700
1700
  }
1701
1701
  var isNoticeMessage = (message) => message.parts.some((part) => part.type === AGENT_NOTICE_PART_TYPE);
1702
1702
  var isHumanMessage = (message) => message.role === "user" && !isNoticeMessage(message) && !isStateMessage(message);
1703
+ var hasChannelPart = (message) => message.parts.some((part) => part.type === CHANNEL_PART_TYPE);
1703
1704
  function turnChannelOf(branch) {
1704
- const human = branch.findLast(isHumanMessage);
1705
- return human ? channelOfMessage(human) : { channel: "web" };
1705
+ const source = branch.findLast((message) => isHumanMessage(message) || message.role === "user" && isNoticeMessage(message) && hasChannelPart(message));
1706
+ return source ? channelOfMessage(source) : { channel: "web" };
1707
+ }
1708
+ function inheritedGroupPart(scope) {
1709
+ return scope.group ? channelPart(scope.channel, scope.group) : void 0;
1706
1710
  }
1707
1711
  function turnOriginOf(branch) {
1708
1712
  const parent = branch.at(-2);
@@ -4656,6 +4660,11 @@ function withStaffRepliesJoined(history) {
4656
4660
  return changed ? joined : history;
4657
4661
  }
4658
4662
 
4663
+ // src/compaction/summary-part.ts
4664
+ var SUMMARY = /* @__PURE__ */ Symbol("compaction-summary");
4665
+ var summaryPart = (text) => ({ type: "text", text, [SUMMARY]: true });
4666
+ var isSummaryPart = (part) => part[SUMMARY] === true;
4667
+
4659
4668
  // src/frozen-context/frozen-context-messages.ts
4660
4669
  function frozenContextBase(history) {
4661
4670
  for (let index = history.length - 1; index >= 0; index -= 1) {
@@ -4670,6 +4679,11 @@ var RESUME_NOTE_ID = "frozen-context-note-";
4670
4679
  function withFrozenContext(history, options2) {
4671
4680
  const { current, group } = options2;
4672
4681
  const dataOf = (message) => message.role !== "assistant" ? void 0 : current && message.id === current.answerId ? current.data : frozenContextOf(message.parts);
4682
+ const root = history[0];
4683
+ const anchorHistory = options2.anchorHistory && options2.anchorHistory[0]?.id === root?.id ? options2.anchorHistory : history;
4684
+ const allowed = options2.includeKey ?? (() => true);
4685
+ const anchor = root?.parts.some(isSummaryPart) ? anchorHistory.flatMap((message) => message.role === "assistant" ? [dataOf(message), ...message.parts.filter((p) => p.type === FROZEN_CONTEXT_PART_TYPE).slice(1).map((p) => p.data)] : []).concat(current?.data).find((data) => data?.appContextFrom === root.id) : void 0;
4686
+ let anchored = false;
4673
4687
  const seen = /* @__PURE__ */ new Map();
4674
4688
  const output = [];
4675
4689
  const place = (texts) => {
@@ -4679,22 +4693,28 @@ function withFrozenContext(history, options2) {
4679
4693
  if (last?.role === "user") output[output.length - 1] = { ...last, parts: [...last.parts, ...parts] };
4680
4694
  else output.push({ id: `frozen-context-${output.length}`, role: "user", parts: output.length === 0 ? [{ type: "text", text: GREETING_LEAD }, ...parts] : parts });
4681
4695
  };
4682
- const textsOf = (data) => blockTexts(data, data.base === null ? void 0 : seen.get(data.base), group);
4696
+ const textsOf = (data, base) => {
4697
+ const include = (key) => allowed(key) && (!anchor || anchored || isReservedKey(key));
4698
+ const texts = blockTexts(data, base, group, anchor || options2.includeKey ? include : void 0);
4699
+ if (data === anchor) anchored = true;
4700
+ return texts;
4701
+ };
4683
4702
  let currentPlaced = false;
4684
4703
  for (const message of history) {
4685
4704
  const data = dataOf(message);
4686
4705
  if (data) {
4687
- place(textsOf(data));
4706
+ place(textsOf(data, data.base === null ? void 0 : seen.get(data.base)));
4688
4707
  seen.set(message.id, data);
4689
4708
  if (message.id === current?.answerId) currentPlaced = true;
4690
4709
  }
4691
- output.push(...withResumeNotes(message, (note) => {
4692
- const texts = blockTexts(note, seen.get(message.id), group);
4710
+ const placed = anchor && message === root ? { ...message, parts: message.parts.flatMap((part) => isSummaryPart(part) ? [part, ...anchor.blocks.filter((block) => !isReservedKey(block.key) && allowed(block.key) && !(group && block.private)).map((block) => ({ type: "text", text: block.text }))] : [part]) } : message;
4711
+ output.push(...withResumeNotes(placed, (note) => {
4712
+ const texts = textsOf(note, seen.get(message.id));
4693
4713
  seen.set(message.id, note);
4694
4714
  return texts;
4695
4715
  }));
4696
4716
  }
4697
- if (current && !currentPlaced) place(textsOf(current.data));
4717
+ if (current && !currentPlaced) place(textsOf(current.data, current.data.base === null ? void 0 : seen.get(current.data.base)));
4698
4718
  return output;
4699
4719
  }
4700
4720
  function withResumeNotes(message, textsOf) {
@@ -4724,11 +4744,11 @@ function holdNoteAfterApprovals(messages) {
4724
4744
  return { messages: messages.slice(0, -1), note };
4725
4745
  }
4726
4746
  var awaitsApprovedRun = (part) => part.state === "approval-responded";
4727
- function blockTexts(data, base, group) {
4728
- return [...changedBlockTexts(data, base, group), ...(data.notices ?? []).map(noticeText)];
4747
+ function blockTexts(data, base, group, include) {
4748
+ return [...changedBlockTexts(data, base, group, include), ...(data.notices ?? []).map(noticeText)];
4729
4749
  }
4730
- function changedBlockTexts(data, base, group) {
4731
- const shown = (block) => !(group && block.private);
4750
+ function changedBlockTexts(data, base, group, include) {
4751
+ const shown = (block) => (!include || include(block.key)) && !(group && block.private);
4732
4752
  if (data.sent === "all" || !base) return data.blocks.filter(shown).map((block) => block.text);
4733
4753
  const now = new Map(data.blocks.map((block) => [block.key, block]));
4734
4754
  const before = new Map(base.blocks.map((block) => [block.key, block]));
@@ -4736,7 +4756,7 @@ function changedBlockTexts(data, base, group) {
4736
4756
  const block = now.get(key);
4737
4757
  return block && shown(block) ? [block.text] : [];
4738
4758
  });
4739
- const visibleKeys = (keys) => keys.filter((key) => !(group && (before.get(key)?.private ?? isMemoryKey(key))));
4759
+ const visibleKeys = (keys) => keys.filter((key) => (!include || include(key)) && !(group && (before.get(key)?.private ?? isMemoryKey(key))));
4740
4760
  const removed = visibleKeys(data.sent.removed);
4741
4761
  if (removed.length > 0) texts.push(noLongerValid(removed));
4742
4762
  const hidden = visibleKeys(data.sent.hidden ?? []);
@@ -5106,7 +5126,10 @@ async function buildModelRequest(input) {
5106
5126
  const cleaned = sendableHistory(input.serviceBlocks ? withoutForgedBlocks(untitled, input.serviceBlocks) : untitled);
5107
5127
  const sendable = input.switchableTools ? withSwitchedOffCallsAsText(cleaned, input.switchableTools, tools) : cleaned;
5108
5128
  const noted = withStaffRepliesJoined(withChannelNotes(withModeNotes(withBurstsJoined(withCorrectionsJoined(sendable)), input.modeNotes)));
5109
- const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, input.frozenContext)) : { messages: noted };
5129
+ const held = input.frozenContext ? holdNoteAfterApprovals(withFrozenContext(noted, {
5130
+ ...input.frozenContext,
5131
+ ...input.frozenContext.anchorHistory && { anchorHistory: sendableHistory(input.frozenContext.anchorHistory) }
5132
+ })) : { messages: noted };
5110
5133
  const history = selectReasoningForModel(held.messages, {
5111
5134
  modelId: profile.id,
5112
5135
  providerId: provider.id,
@@ -8620,7 +8643,8 @@ function shortOutput(value) {
8620
8643
  var MAX_SERVER_TURNS_IN_ROW = 5;
8621
8644
  function noticeMessageParts(input) {
8622
8645
  const data = { notices: input.notices, deliveryKey: input.deliveryKey, wakeAgent: input.wakeAgent };
8623
- return [{ type: AGENT_NOTICE_PART_TYPE, data }];
8646
+ const channel = channelPart(input.channel ?? "web", input.group);
8647
+ return [{ type: AGENT_NOTICE_PART_TYPE, data }, ...channel ? [channel] : []];
8624
8648
  }
8625
8649
  function noticeDataOf(message) {
8626
8650
  if (message.role !== "user") return void 0;
@@ -11577,7 +11601,7 @@ function tokens(count) {
11577
11601
  }
11578
11602
  function compactionTask(input) {
11579
11603
  if (input.instruction !== void 0) return input.instruction;
11580
- return `\u0421\u0434\u0435\u043B\u0430\u0439 \u0441\u0432\u043E\u0434\u043A\u0443 \u0432\u0441\u0435\u0439 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438 \u0432\u044B\u0448\u0435, \u0447\u0442\u043E\u0431\u044B \u043C\u043E\u0436\u043D\u043E \u0431\u044B\u043B\u043E \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0431\u0435\u0437 \u0441\u0442\u0430\u0440\u044B\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439.
11604
+ return `\u0421\u0434\u0435\u043B\u0430\u0439 \u0441\u0432\u043E\u0434\u043A\u0443 \u0441\u0436\u0438\u043C\u0430\u0435\u043C\u043E\u0439 \u0447\u0430\u0441\u0442\u0438 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438 \u0432\u044B\u0448\u0435. \u041E\u043D\u0430 \u0437\u0430\u043A\u0430\u043D\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u043F\u043E\u0441\u043B\u0435\u0434\u043D\u0435\u043C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0438 \u043F\u0435\u0440\u0435\u0434 \u044D\u0442\u0438\u043C \u0437\u0430\u0434\u0430\u043D\u0438\u0435\u043C; \u0431\u043E\u043B\u0435\u0435 \u043F\u043E\u0437\u0434\u043D\u044F\u044F \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0430 \u043E\u0441\u0442\u0430\u043D\u0435\u0442\u0441\u044F \u0432 \u043A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u0435 \u043E\u0442\u0434\u0435\u043B\u044C\u043D\u043E. \u041F\u0435\u0440\u0435\u0441\u043A\u0430\u0437 \u0434\u043E\u043B\u0436\u0435\u043D \u043E\u0442\u0440\u0430\u0436\u0430\u0442\u044C \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0433\u0440\u0430\u043D\u0438\u0446\u0435, \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u043C \u0445\u0432\u043E\u0441\u0442\u043E\u043C, \u0431\u0435\u0437 \u043F\u043E\u0432\u0442\u043E\u0440\u0435\u043D\u0438\u044F \u0443\u0436\u0435 \u0441\u0434\u0435\u043B\u0430\u043D\u043D\u043E\u0433\u043E \u0432 \u043D\u0451\u043C.
11581
11605
 
11582
11606
  \u0415\u0441\u043B\u0438 \u0432 \u043D\u0430\u0447\u0430\u043B\u0435 \u043F\u0435\u0440\u0435\u043F\u0438\u0441\u043A\u0438 \u0443\u0436\u0435 \u0435\u0441\u0442\u044C \u0441\u0432\u043E\u0434\u043A\u0430, \u043D\u0435 \u0434\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0439 \u043A \u043D\u0435\u0439, \u0430 \u043F\u0435\u0440\u0435\u043F\u0438\u0448\u0438 \u0435\u0451 \u0446\u0435\u043B\u0438\u043A\u043E\u043C \u0432\u043C\u0435\u0441\u0442\u0435 \u0441 \u043D\u043E\u0432\u044B\u043C\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F\u043C\u0438: \u043F\u043E\u043B\u0443\u0447\u0438\u0442\u0441\u044F \u043E\u0434\u043D\u0430 \u0441\u0432\u043E\u0434\u043A\u0430.
11583
11607
 
@@ -11608,10 +11632,10 @@ ${summary}
11608
11632
  function applyCompaction(branch, active) {
11609
11633
  if (!active) return branch.map(toAgentChatMessage);
11610
11634
  const tail = branch.slice(active.throughIndex + 1).map(toAgentChatMessage);
11611
- const summaryPart = { type: "text", text: summaryFrame(active.record.summary) };
11635
+ const summary = summaryPart(summaryFrame(active.record.summary));
11612
11636
  const [first, ...rest] = tail;
11613
- if (first?.role === "user") return [{ ...first, parts: [summaryPart, ...first.parts] }, ...rest];
11614
- return [{ id: `${active.record.id}-summary`, role: "user", parts: [summaryPart] }, ...tail];
11637
+ if (first?.role === "user") return [{ ...first, parts: [summary, ...first.parts] }, ...rest];
11638
+ return [{ id: `${active.record.id}-summary`, role: "user", parts: [summary] }, ...tail];
11615
11639
  }
11616
11640
 
11617
11641
  // src/compaction/run-compaction.ts
@@ -11643,6 +11667,13 @@ async function runCompaction(input) {
11643
11667
  const request = await buildModelRequest({
11644
11668
  ...requestInput,
11645
11669
  ...input.model,
11670
+ // Общая сводка не несёт privacy-схему: только публичные app blocks, без памяти/даты/места.
11671
+ // Пересказ шагов остаётся на прежнем пути без frozen blocks; никакого нового private opt-in.
11672
+ ...!input.transient && { frozenContext: {
11673
+ group: true,
11674
+ includeKey: (key) => !isReservedKey(key),
11675
+ ...input.contextHistory && { anchorHistory: input.contextHistory }
11676
+ } },
11646
11677
  // Бюджет рассуждений — от предела сводки, а не от потолка модели: иначе он больше предела.
11647
11678
  maxOutputTokens,
11648
11679
  ...lowestLevel && { reasoning: lowestLevel },
@@ -11653,7 +11684,7 @@ async function runCompaction(input) {
11653
11684
  role: "user",
11654
11685
  content: input.task ?? compactionTask({ summaryCeilingTokens: settings.summaryCeilingTokens, instruction: settings.instruction })
11655
11686
  };
11656
- let base = [...request.messages, task];
11687
+ let base = [...request.messages, ...request.noteAfterApprovals ?? [], task];
11657
11688
  let messages = base;
11658
11689
  let summary = "";
11659
11690
  let summaryTokens = 0;
@@ -11788,7 +11819,8 @@ function createCompactionService(options2) {
11788
11819
  }
11789
11820
  overflow?.onStart();
11790
11821
  model = settings.model ?? model;
11791
- const history = applyCompaction(branch.slice(0, plan.throughIndex + 1), activeCompaction(branch, compactions));
11822
+ const active = activeCompaction(branch, compactions);
11823
+ const history = applyCompaction(branch.slice(0, plan.throughIndex + 1), active);
11792
11824
  const chat = await storage.getChat(chatId);
11793
11825
  const ownerId = chat?.ownerId ?? "";
11794
11826
  const result = await runCompaction({
@@ -11801,6 +11833,7 @@ function createCompactionService(options2) {
11801
11833
  ...options2.switchableTools && { switchableTools: options2.switchableTools },
11802
11834
  ...observation(ownerId, answerId),
11803
11835
  history,
11836
+ contextHistory: applyCompaction(branch, active),
11804
11837
  ...options2.attachments && { attachments: options2.attachments },
11805
11838
  ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
11806
11839
  settings,
@@ -11945,6 +11978,7 @@ function createCompactionService(options2) {
11945
11978
  ...options2.switchableTools && { switchableTools: options2.switchableTools },
11946
11979
  ...observation(ownerId, answerId),
11947
11980
  history: [...history, { id: `${chatId}-answer-steps`, role: "assistant", parts: steps }],
11981
+ transient: true,
11948
11982
  ...options2.attachments && { attachments: options2.attachments },
11949
11983
  ...options2.serviceBlocks && { serviceBlocks: options2.serviceBlocks },
11950
11984
  settings,
@@ -13159,9 +13193,9 @@ function createChatService(input) {
13159
13193
  throw error;
13160
13194
  }
13161
13195
  }
13162
- async function confirmFiles(messageId, fileParts) {
13196
+ async function confirmFiles(fileParts) {
13163
13197
  if (fileParts.length === 0) return;
13164
- await options2.attachments.confirmUserParts(fileParts).catch((error) => logError(`\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u0442\u043C\u0435\u0442\u0438\u0442\u044C \u0432\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F ${logValue(messageId)}: ${String(error)}`));
13198
+ await options2.attachments.confirmUserParts(fileParts);
13165
13199
  }
13166
13200
  async function ownChat(userId, chatId) {
13167
13201
  const chat = await storage.getChat(chatId);
@@ -13878,7 +13912,7 @@ function createChatService(input) {
13878
13912
  // Окно тишины пачки считается от этого времени: по часам службы.
13879
13913
  ...burst && { createdAt: writtenAt.toISOString() }
13880
13914
  });
13881
- await confirmFiles(question3.id, fileParts);
13915
+ await confirmFiles(fileParts);
13882
13916
  await storage.insertMessage(question3);
13883
13917
  return question3;
13884
13918
  });
@@ -14114,14 +14148,20 @@ function createChatService(input) {
14114
14148
  return await writingCountedMessage(userId, chatId, async () => {
14115
14149
  const queued = await steering.accept(chatId, async (parentId) => {
14116
14150
  const fileParts = await acceptFiles(userId, chatId, files);
14151
+ const groupPart = inheritedGroupPart(turnChannelOf(await currentBranch(chatId)));
14117
14152
  const correction = newStoredMessage({
14118
14153
  id: message.id,
14119
14154
  chatId,
14120
14155
  parentId,
14121
14156
  role: "user",
14122
- parts: [...await filePartsWithNumbers(chatId, fileParts), ...text.trim().length > 0 ? [{ type: "text", text }] : [], steerPart()]
14157
+ parts: [
14158
+ ...await filePartsWithNumbers(chatId, fileParts),
14159
+ ...text.trim().length > 0 ? [{ type: "text", text }] : [],
14160
+ steerPart(),
14161
+ ...groupPart ? [groupPart] : []
14162
+ ]
14123
14163
  });
14124
- await confirmFiles(correction.id, fileParts);
14164
+ await confirmFiles(fileParts);
14125
14165
  await storage.insertMessage(correction);
14126
14166
  return correction;
14127
14167
  }, page);
@@ -14409,7 +14449,7 @@ function createChatService(input) {
14409
14449
  await compaction.cancel(chatId);
14410
14450
  await options2.attachments?.removeChatFiles?.(chatId);
14411
14451
  await storage.deleteChat(chatId);
14412
- }).finally(() => deleting.delete(chatId));
14452
+ }, { lease: "required" }).finally(() => deleting.delete(chatId));
14413
14453
  await options2.background?.chatDeleted(userId, chatId).catch((error) => logError(`\u0424\u043E\u043D: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0443\u0431\u0440\u0430\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0438 \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u043E\u0433\u043E \u0447\u0430\u0442\u0430 ${chatId}: ${String(error)}`));
14414
14454
  await options2.limits?.chatDeleted?.({ userId, chatId }).catch((error) => logError(`\u041B\u0438\u043C\u0438\u0442\u044B: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0441\u0442\u0435\u0440\u0435\u0442\u044C \u043E\u043A\u043D\u0430 \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u043E\u0433\u043E \u0447\u0430\u0442\u0430 ${chatId}: ${String(error)}`));
14415
14455
  },
@@ -14591,11 +14631,13 @@ function createChatService(input) {
14591
14631
  )
14592
14632
  ] : [],
14593
14633
  // Правленое сообщение пачки остаётся в своей пачке: отметка с прежним временем записи.
14594
- ...edited2.parts.filter((part) => part.type === BURST_PART_TYPE)
14634
+ ...edited2.parts.filter((part) => part.type === BURST_PART_TYPE),
14635
+ // Правленое сообщение группы остаётся в границах группы: отметка — из записанного сообщения, не из запроса.
14636
+ ...[inheritedGroupPart(channelOfMessage(edited2))].filter((part) => part !== void 0)
14595
14637
  ],
14596
14638
  createdAt: nextSiblingTime(messages, selections, edited2.parentId)
14597
14639
  });
14598
- await confirmFiles(question3.id, fileParts);
14640
+ await confirmFiles(fileParts);
14599
14641
  await storage.insertMessage(question3);
14600
14642
  return { branch: branch2, index: index2, edited: edited2, question: question3 };
14601
14643
  });
@@ -15199,6 +15241,7 @@ export {
15199
15241
  THOUGHT_TITLE_METADATA_KEY,
15200
15242
  thoughtTitlesOfParts,
15201
15243
  CHANNEL_PART_TYPE,
15244
+ isHumanMessage,
15202
15245
  turnOriginOf,
15203
15246
  GREETING_LEAD,
15204
15247
  STEER_PART_TYPE,
@@ -15323,4 +15366,4 @@ export {
15323
15366
  InvalidWidgetAnswerError,
15324
15367
  createChatService
15325
15368
  };
15326
- //# sourceMappingURL=chunk-Z44ZAFZC.js.map
15369
+ //# sourceMappingURL=chunk-YGHGURNF.js.map