@ykurilov/agent-chat-flows 0.55.0 → 0.57.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,58 @@
1
1
  # @ykurilov/agent-chat-flows
2
2
 
3
+ ## 0.57.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7c82b87: Перевод чатов на новую версию теперь отбирает чаты. `flow.chatsByState` и `flow.migrateChats` по умолчанию не берут
8
+ закончившиеся разговоры и тестовые чаты редактора (`FlowTestChat`). Свой отбор — `chatIds` и `filter(chat)` (запись
9
+ прогона и `Chat.data`), одинаково в обоих вызовах: что посчитали, то и переведут. Вернуть прежнее поведение —
10
+ `includeEnded: true, includeTestChats: true`. Окно «Перевести чаты» после перевода перечитывает счётчик.
11
+
12
+ ### Как обновить приложение
13
+
14
+ 1. Если приложение держало чужие строки `agent_chat.flow_chats` своей транзакцией (`for update`), чтобы `migrateChats`
15
+ их пропустил, — уберите этот обход и передайте отбор прямо в вызовы:
16
+
17
+ Было:
18
+
19
+ ```ts
20
+ await client.query("select chat_id from agent_chat.flow_chats where ... for update", [...]);
21
+ await flow.migrateChats({ flowId, toVersionId, map, flow: next });
22
+ ```
23
+
24
+ Стало:
25
+
26
+ ```ts
27
+ await flow.migrateChats({ flowId, toVersionId, map, flow: next, chatIds });
28
+ ```
29
+
30
+ В `chatsByState` передайте тот же отбор (`chatIds` или `filter`).
31
+ 2. Нужны закончившиеся разговоры или тестовые чаты в переводе — добавьте `includeEnded: true` или
32
+ `includeTestChats: true` в оба вызова.
33
+ 3. Своё хранилище сценариев (`FlowStore`) — добавьте метод `listFlowChats({ flowId, exceptVersionId?, chatIds? })` и
34
+ поддержку `chatIds` в `migrateBatch`. Хранилища SDK (память, SQLite, PostgreSQL) уже умеют.
35
+ 4. Проверка: `pnpm typecheck` проходит; окно «Перевести чаты» показывает число без закончившихся и тестовых чатов.
36
+
37
+ ### Patch Changes
38
+
39
+ - 34074f9: Шапка доски сценария на телефоне: лишние кнопки уходят в меню «⋯», Прогон, Проверить граф и сохранение остаются на виду.
40
+ Новая подпись `labels.header.more`.
41
+ - Updated dependencies [ade99cc]
42
+ - Updated dependencies [86afa20]
43
+ - Updated dependencies [624c10c]
44
+ - @ykurilov/agent-chat-core@0.57.0
45
+ - @ykurilov/agent-chat-react@0.57.0
46
+
47
+ ## 0.56.0
48
+
49
+ ### Patch Changes
50
+
51
+ - Updated dependencies [1eadba9]
52
+ - Updated dependencies [24c313e]
53
+ - @ykurilov/agent-chat-react@0.56.0
54
+ - @ykurilov/agent-chat-core@0.56.0
55
+
3
56
  ## 0.55.0
4
57
 
5
58
  ### Minor Changes
package/dist/index.js CHANGED
@@ -118,6 +118,9 @@ import {
118
118
 
119
119
  // src/runtime/agent-flow.ts
120
120
  import { createHash, randomUUID as randomUUID4 } from "crypto";
121
+ import {
122
+ ChatNotFoundError as ChatNotFoundError4
123
+ } from "@ykurilov/agent-chat-core";
121
124
 
122
125
  // src/prompt/rules/time-zone.ts
123
126
  var TIME_ZONE_RULES = `## \u0414\u043D\u0438 \u0438 \u0432\u0440\u0435\u043C\u044F
@@ -1679,6 +1682,7 @@ function createFlowTimers(input) {
1679
1682
 
1680
1683
  // src/runtime/migrate-chats.ts
1681
1684
  var MIGRATE_BATCH = 100;
1685
+ var chunks = (items, size) => Array.from({ length: Math.ceil(items.length / size) }, (_, index) => items.slice(index * size, (index + 1) * size));
1682
1686
  function createChatMigrator(deps) {
1683
1687
  const { store } = deps;
1684
1688
  async function newVersion(flowId, versionId, raw) {
@@ -1699,28 +1703,53 @@ function createChatMigrator(deps) {
1699
1703
  if (!found) throw new Error(`\u0412\u0435\u0440\u0441\u0438\u0438 ${versionId} \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \xAB${flowId}\xBB \u043D\u0435\u0442 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0435\u0451 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u0432 \u043F\u043E\u043B\u0435 flow.`);
1700
1704
  return { flow: found.flow };
1701
1705
  }
1706
+ async function selectedChats(flowId, exceptVersionId, selection) {
1707
+ const rows = await store.listFlowChats({ flowId, ...exceptVersionId !== void 0 && { exceptVersionId }, ...selection.chatIds && { chatIds: selection.chatIds } });
1708
+ const needsData = selection.filter !== void 0 || selection.includeTestChats !== true;
1709
+ const candidate = async ({ endedAt, ...row }) => {
1710
+ const data = needsData ? await deps.chatData(row) : {};
1711
+ const test = testChatOffset(data) !== null;
1712
+ if (test && selection.includeTestChats !== true) return null;
1713
+ const chat = { ...row, ended: endedAt !== null, test, data };
1714
+ return !selection.filter || await selection.filter(chat) ? chat : null;
1715
+ };
1716
+ const selected = [];
1717
+ for (const part of chunks(rows.filter((row) => row.endedAt === null || selection.includeEnded === true), MIGRATE_BATCH)) {
1718
+ for (const chat of await Promise.all(part.map(candidate))) if (chat) selected.push(chat);
1719
+ }
1720
+ return selected;
1721
+ }
1722
+ function countByState(chats) {
1723
+ const counts = /* @__PURE__ */ new Map();
1724
+ for (const chat of chats) {
1725
+ const key = JSON.stringify([chat.versionId, chat.stateId]);
1726
+ const entry = counts.get(key) ?? { versionId: chat.versionId, stateId: chat.stateId, chats: 0 };
1727
+ entry.chats += 1;
1728
+ counts.set(key, entry);
1729
+ }
1730
+ return [...counts.values()].sort(byVersionAndState);
1731
+ }
1702
1732
  return {
1703
- async chatsByState(flowId, options) {
1704
- const rows = await store.countChatsByState({ flowId });
1705
- return options?.exceptVersionId === void 0 ? rows : rows.filter((row) => row.versionId !== options.exceptVersionId);
1733
+ async chatsByState(flowId, options = {}) {
1734
+ return countByState(await selectedChats(flowId, options.exceptVersionId, options));
1706
1735
  },
1707
- async migrateChats({ flowId, toVersionId, map, flow: raw }) {
1736
+ async migrateChats(input) {
1737
+ const { flowId, toVersionId, map, flow: raw } = input;
1708
1738
  const { flow: next, save } = await newVersion(flowId, toVersionId, raw);
1709
1739
  const removedMap = Object.fromEntries(Object.entries(map).filter(([stateId]) => !stateById(next, stateId)));
1710
1740
  for (const [from, to] of Object.entries(removedMap)) {
1711
1741
  if (!stateById(next, to)) throw new Error(`\u041A\u0443\u0434\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B \u0438\u0437 \xAB${from}\xBB: \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \xAB${to}\xBB \u043D\u0435\u0442 \u0432 \u0432\u0435\u0440\u0441\u0438\u0438 ${toVersionId}.`);
1712
1742
  }
1713
- const counts = await store.countChatsByState({ flowId });
1714
- const lost = [...new Set(counts.filter((row) => row.versionId !== toVersionId && row.chats > 0 && !stateById(next, row.stateId) && removedMap[row.stateId] === void 0).map((row) => row.stateId))];
1743
+ const chats = await selectedChats(flowId, toVersionId, input);
1744
+ const lost = [...new Set(chats.filter((chat) => !stateById(next, chat.stateId) && removedMap[chat.stateId] === void 0).map((chat) => chat.stateId))];
1715
1745
  if (lost.length > 0) {
1716
1746
  throw new Error(`\u0412 \u0432\u0435\u0440\u0441\u0438\u0438 ${toVersionId} \u043D\u0435\u0442 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 ${lost.map((id) => `\xAB${id}\xBB`).join(", ")}, \u0430 \u0432 \u043D\u0438\u0445 \u0435\u0441\u0442\u044C \u0447\u0430\u0442\u044B: \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u0443\u0434\u0430 \u0438\u0445 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438.`);
1717
1747
  }
1718
1748
  await save?.();
1719
1749
  const at = deps.now().toISOString();
1720
1750
  let moved = 0;
1721
- for (; ; ) {
1722
- const batch = await store.migrateBatch({ flowId, toVersionId, map: removedMap, limit: MIGRATE_BATCH, at });
1723
- if (batch.moved.length === 0) break;
1751
+ for (const part of chunks(chats.map((chat) => chat.chatId), MIGRATE_BATCH)) {
1752
+ const batch = await store.migrateBatch({ flowId, toVersionId, map: removedMap, chatIds: part, limit: part.length, at });
1724
1753
  moved += batch.moved.length;
1725
1754
  for (const chatId of batch.moved) {
1726
1755
  const record = await store.getChat(chatId);
@@ -2376,7 +2405,8 @@ function buildAgentFlow(options, modules = {}) {
2376
2405
  now: clock,
2377
2406
  checkedFlow: (loaded) => checkedFlow(loaded.flowId, loaded.versionId, loaded.flow),
2378
2407
  saveVersion: saveChecked,
2379
- rescheduleMigrated
2408
+ rescheduleMigrated,
2409
+ chatData
2380
2410
  })
2381
2411
  );
2382
2412
  const background = /* @__PURE__ */ new Set();
@@ -2603,6 +2633,15 @@ function buildAgentFlow(options, modules = {}) {
2603
2633
  const run = storedRun(values[FLOW_STATE_KEY]);
2604
2634
  if (run) await reschedule(ownerId, chatId, run, run.answerId ?? null, at);
2605
2635
  }
2636
+ async function chatData(chat) {
2637
+ if (!port && !attachment) return {};
2638
+ try {
2639
+ return await (await portOf(chat)).getChatData(chat.ownerId, chat.chatId);
2640
+ } catch (error) {
2641
+ if (error instanceof ChatNotFoundError4) return {};
2642
+ throw error;
2643
+ }
2644
+ }
2606
2645
  async function rescheduleMigrated({ record, flow, map }) {
2607
2646
  try {
2608
2647
  const { values } = await (await portOf(record)).getChatState(record.ownerId, record.chatId);
@@ -3029,9 +3068,14 @@ function memoryFlowStore() {
3029
3068
  }
3030
3069
  return [...counts.values()].sort(byVersionAndState);
3031
3070
  },
3071
+ async listFlowChats({ flowId, exceptVersionId, chatIds }) {
3072
+ const only = chatIds && new Set(chatIds);
3073
+ return [...chats.values()].filter((row) => row.flowId === flowId && row.versionId !== exceptVersionId && (!only || only.has(row.chatId))).sort((left, right) => compareText(left.chatId, right.chatId)).map(({ chatId, ownerId, flowId: id, versionId, stateId, endedAt, endReason }) => ({ chatId, ownerId, flowId: id, versionId, stateId, endedAt, endReason }));
3074
+ },
3032
3075
  async migrateBatch(input) {
3033
3076
  const change = migratePending(input);
3034
- const batch = [...chats.values()].filter((row) => row.flowId === input.flowId && row.versionId !== input.toVersionId).sort((left, right) => compareText(left.chatId, right.chatId)).slice(0, input.limit);
3077
+ const only = input.chatIds && new Set(input.chatIds);
3078
+ const batch = [...chats.values()].filter((row) => row.flowId === input.flowId && row.versionId !== input.toVersionId && (!only || only.has(row.chatId))).sort((left, right) => compareText(left.chatId, right.chatId)).slice(0, input.limit);
3035
3079
  for (const row of batch) {
3036
3080
  row.versionId = input.toVersionId;
3037
3081
  row.stateId = mappedState(row.stateId, input.map);