@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/dist/sqlite.js CHANGED
@@ -230,9 +230,33 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
230
230
  const rows = db.select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql`count(*)` }).from(flowChats).where(eq(flowChats.flowId, flowId)).groupBy(flowChats.versionId, flowChats.stateId).all();
231
231
  return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);
232
232
  },
233
+ async listFlowChats({ flowId, exceptVersionId, chatIds }) {
234
+ if (chatIds?.length === 0) return [];
235
+ const rows = db.select({
236
+ chatId: flowChats.chatId,
237
+ ownerId: flowChats.ownerId,
238
+ flowId: flowChats.flowId,
239
+ versionId: flowChats.versionId,
240
+ stateId: flowChats.stateId,
241
+ endedAt: flowChats.endedAt,
242
+ endReason: flowChats.endReason
243
+ }).from(flowChats).where(
244
+ and(
245
+ eq(flowChats.flowId, flowId),
246
+ exceptVersionId === void 0 ? void 0 : ne(flowChats.versionId, exceptVersionId),
247
+ chatIds === void 0 ? void 0 : inArray(flowChats.chatId, chatIds)
248
+ )
249
+ ).orderBy(asc(flowChats.chatId)).all();
250
+ return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));
251
+ },
233
252
  async migrateBatch(input) {
234
253
  const change = migratePending(input);
235
- const notYetMoved = and(eq(flowChats.flowId, input.flowId), ne(flowChats.versionId, input.toVersionId));
254
+ if (input.chatIds?.length === 0) return { moved: [] };
255
+ const notYetMoved = and(
256
+ eq(flowChats.flowId, input.flowId),
257
+ ne(flowChats.versionId, input.toVersionId),
258
+ input.chatIds === void 0 ? void 0 : inArray(flowChats.chatId, input.chatIds)
259
+ );
236
260
  const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);
237
261
  const moved = db.update(flowChats).set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) }).where(and(inArray(flowChats.chatId, batch), notYetMoved)).returning({ chatId: flowChats.chatId }).all();
238
262
  return { moved: moved.map((row) => row.chatId) };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sqlite.ts","../src/store/sqlite.ts","../src/store/sqlite-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { createSqliteFlowStore, type SqliteFlowStore } from \"./store/sqlite\";\n\nexport type { SqliteFlowStore } from \"./store/sqlite\";\nexport { FLOWS_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/sqlite-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/sqlite`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/sqlite\", import.meta.url));\n\n/**\n * Сценарии в файле SQLite; файл может быть общим с хранилищем чатов (`createSqliteChatStorage`).\n * Таблицы приводятся к последней миграции при открытии.\n */\nexport function sqliteFlowStore(filePath: string): SqliteFlowStore {\n return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);\n}\n","import Database from \"better-sqlite3\";\nimport { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { migrate } from \"drizzle-orm/better-sqlite3/migrator\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { appliedInTime, byVersionAndState, compareText, migratePending, pendingAdded } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./sqlite-schema\";\n\n/** Хранилище в файле SQLite: `close` обязателен — файл закрывает приложение при остановке. */\nexport type SqliteFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toMs = (iso: string) => Date.parse(iso);\nconst toIso = (ms: number) => new Date(ms).toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** Дописать изменение в конец `pending`, если такого ключа там ещё нет. Одно выражение — одна запись базы. */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})\n then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: number) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/**\n * Сценарии в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим с хранилищем\n * чатов. Несколько копий сервера на одном файле делят таймеры арендой: каждое изменение — одно условное `update`\n * или короткая транзакция с захватом записи (`immediate`).\n */\nexport function createSqliteFlowStore(filePath: string, migrationsFolder: string): SqliteFlowStore {\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"busy_timeout = 5000\");\n const db = drizzle(sqlite);\n migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });\n\n const getChat = (chatId: string) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n\n return {\n async pinChat(input) {\n db.insert(flowChats)\n .values({\n chatId: input.chatId,\n ownerId: input.ownerId,\n flowId: input.flowId,\n versionId: input.versionId,\n stateId: input.stateId,\n pending: [],\n timeZone: input.timeZone,\n ...(input.placement && { placement: input.placement }),\n updatedAt: toMs(input.at),\n })\n .onConflictDoNothing()\n .run();\n return toRecord(getChat(input.chatId)!);\n },\n async getChat(chatId) {\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n const row = db\n .update(flowChats)\n .set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async addPending(chatId, change) {\n // Прочитать и дописать под захватом записи: две копии с одним ключом не дают двух записей.\n const add = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n const next = row && pendingAdded(row.pending, change);\n if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();\n });\n add.immediate();\n },\n async takePending(chatId) {\n // Прочитать и очистить под захватом записи: вторая копия ждёт и видит уже пустой список (кроме применённых в срок).\n const take = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n if (!row || row.pending.length === 0) return [];\n db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();\n return row.pending;\n });\n return take.immediate();\n },\n\n async setDue(chatId, due, options) {\n db.update(flowChats)\n .set({\n dueAt: due ? toMs(due.at) : null,\n dueExitId: due?.exitId ?? null,\n dueBranchHead: due?.branchHead ?? null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined))\n .run();\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n const nowMs = toMs(now);\n // Выбор и аренда — одно `update`: из двух копий, пришедших одновременно, строку получит только первая.\n const due = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowMs))\n .orderBy(asc(flowChats.dueAt), asc(flowChats.chatId))\n .limit(limit);\n const rows = db\n .update(flowChats)\n .set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs })\n .where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs)))\n .returning()\n .all();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n db.update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .run();\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n const renewed = db\n .update(flowChats)\n .set({ leaseUntil: toMs(now) + leaseMs })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .returning({ chatId: flowChats.chatId })\n .all();\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n const inserted = db\n .insert(flowVersions)\n .values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash })\n .all();\n if (inserted.length > 0) return \"new\";\n const saved = db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId)))\n .get();\n return saved?.hash === input.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n const row = db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)))\n .get();\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n const rows = db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId)\n .all();\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async migrateBatch(input) {\n const change = migratePending(input);\n const notYetMoved = and(eq(flowChats.flowId, input.flowId), ne(flowChats.versionId, input.toVersionId));\n const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);\n const moved = db\n .update(flowChats)\n .set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId })\n .all();\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();\n return rows\n .map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };\n db.insert(flowClientCards)\n .values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry })\n .run();\n },\n async forgetCard(ownerId, fieldId) {\n db.delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)))\n .run();\n },\n\n async endChat(chatId, { reason, at }) {\n db.update(flowChats)\n .set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)))\n .run();\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n const row = db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : toMs(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDayClosed(chatId, day) {\n db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDelivery(chatId, delivery) {\n const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };\n db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async readTimeZone(ownerId) {\n return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n db.insert(flowPeople)\n .values({ ownerId, timeZone, updatedAt: toMs(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } })\n .run();\n },\n\n async readSideTasks(ownerId, chatId) {\n const rows = db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))))\n .all();\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async forgetSideTasks(ownerId) {\n db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();\n },\n async noteSideTask(input) {\n const asked = input.status === \"asked\";\n const doneAt = asked ? null : toMs(input.at);\n const row = db\n .insert(flowSideTasks)\n .values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` },\n })\n .returning()\n .get();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n const row = db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)))\n .get();\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n const row = db\n .insert(flowFirstMessages)\n .values({ ownerId, day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count })\n .get();\n return row!.count;\n },\n\n async close() {\n if (sqlite.open) sqlite.close();\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { index, integer, primaryKey, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n// Времена — миллисекунды с 1970 года, JSON — текстом.\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = sqliteTable(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: text(\"pending\", { mode: \"json\" }).$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: integer(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: integer(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: integer(\"updated_at\").notNull(),\n endedAt: integer(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: integer(\"paused_until\"),\n windowClosed: integer(\"window_closed\", { mode: \"boolean\" }).notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: text(\"delivery\", { mode: \"json\" }).$type<FlowPendingDelivery>(),\n placement: text(\"placement\", { mode: \"json\" }).$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = sqliteTable(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: text(\"flow\", { mode: \"json\" }).$type<AgentFlow>().notNull(),\n createdAt: integer(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = sqliteTable(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: text(\"value\", { mode: \"json\" }).notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = sqliteTable(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = sqliteTable(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = sqliteTable(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: integer(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/sqlite.ts","../src/store/sqlite.ts","../src/store/sqlite-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { createSqliteFlowStore, type SqliteFlowStore } from \"./store/sqlite\";\n\nexport type { SqliteFlowStore } from \"./store/sqlite\";\nexport { FLOWS_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/sqlite-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/sqlite`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/sqlite\", import.meta.url));\n\n/**\n * Сценарии в файле SQLite; файл может быть общим с хранилищем чатов (`createSqliteChatStorage`).\n * Таблицы приводятся к последней миграции при открытии.\n */\nexport function sqliteFlowStore(filePath: string): SqliteFlowStore {\n return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);\n}\n","import Database from \"better-sqlite3\";\nimport { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { migrate } from \"drizzle-orm/better-sqlite3/migrator\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { appliedInTime, byVersionAndState, compareText, migratePending, pendingAdded } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./sqlite-schema\";\n\n/** Хранилище в файле SQLite: `close` обязателен — файл закрывает приложение при остановке. */\nexport type SqliteFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toMs = (iso: string) => Date.parse(iso);\nconst toIso = (ms: number) => new Date(ms).toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** Дописать изменение в конец `pending`, если такого ключа там ещё нет. Одно выражение — одна запись базы. */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})\n then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: number) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/**\n * Сценарии в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим с хранилищем\n * чатов. Несколько копий сервера на одном файле делят таймеры арендой: каждое изменение — одно условное `update`\n * или короткая транзакция с захватом записи (`immediate`).\n */\nexport function createSqliteFlowStore(filePath: string, migrationsFolder: string): SqliteFlowStore {\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"busy_timeout = 5000\");\n const db = drizzle(sqlite);\n migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });\n\n const getChat = (chatId: string) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n\n return {\n async pinChat(input) {\n db.insert(flowChats)\n .values({\n chatId: input.chatId,\n ownerId: input.ownerId,\n flowId: input.flowId,\n versionId: input.versionId,\n stateId: input.stateId,\n pending: [],\n timeZone: input.timeZone,\n ...(input.placement && { placement: input.placement }),\n updatedAt: toMs(input.at),\n })\n .onConflictDoNothing()\n .run();\n return toRecord(getChat(input.chatId)!);\n },\n async getChat(chatId) {\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n const row = db\n .update(flowChats)\n .set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async addPending(chatId, change) {\n // Прочитать и дописать под захватом записи: две копии с одним ключом не дают двух записей.\n const add = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n const next = row && pendingAdded(row.pending, change);\n if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();\n });\n add.immediate();\n },\n async takePending(chatId) {\n // Прочитать и очистить под захватом записи: вторая копия ждёт и видит уже пустой список (кроме применённых в срок).\n const take = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n if (!row || row.pending.length === 0) return [];\n db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();\n return row.pending;\n });\n return take.immediate();\n },\n\n async setDue(chatId, due, options) {\n db.update(flowChats)\n .set({\n dueAt: due ? toMs(due.at) : null,\n dueExitId: due?.exitId ?? null,\n dueBranchHead: due?.branchHead ?? null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined))\n .run();\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n const nowMs = toMs(now);\n // Выбор и аренда — одно `update`: из двух копий, пришедших одновременно, строку получит только первая.\n const due = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowMs))\n .orderBy(asc(flowChats.dueAt), asc(flowChats.chatId))\n .limit(limit);\n const rows = db\n .update(flowChats)\n .set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs })\n .where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs)))\n .returning()\n .all();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n db.update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .run();\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n const renewed = db\n .update(flowChats)\n .set({ leaseUntil: toMs(now) + leaseMs })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .returning({ chatId: flowChats.chatId })\n .all();\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n const inserted = db\n .insert(flowVersions)\n .values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash })\n .all();\n if (inserted.length > 0) return \"new\";\n const saved = db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId)))\n .get();\n return saved?.hash === input.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n const row = db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)))\n .get();\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n const rows = db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId)\n .all();\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats({ flowId, exceptVersionId, chatIds }) {\n if (chatIds?.length === 0) return [];\n const rows = db\n .select({\n chatId: flowChats.chatId,\n ownerId: flowChats.ownerId,\n flowId: flowChats.flowId,\n versionId: flowChats.versionId,\n stateId: flowChats.stateId,\n endedAt: flowChats.endedAt,\n endReason: flowChats.endReason,\n })\n .from(flowChats)\n .where(\n and(\n eq(flowChats.flowId, flowId),\n exceptVersionId === undefined ? undefined : ne(flowChats.versionId, exceptVersionId),\n chatIds === undefined ? undefined : inArray(flowChats.chatId, chatIds),\n ),\n )\n .orderBy(asc(flowChats.chatId))\n .all();\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n const change = migratePending(input);\n if (input.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, input.flowId),\n ne(flowChats.versionId, input.toVersionId),\n input.chatIds === undefined ? undefined : inArray(flowChats.chatId, input.chatIds),\n );\n const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);\n const moved = db\n .update(flowChats)\n .set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId })\n .all();\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();\n return rows\n .map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };\n db.insert(flowClientCards)\n .values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry })\n .run();\n },\n async forgetCard(ownerId, fieldId) {\n db.delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)))\n .run();\n },\n\n async endChat(chatId, { reason, at }) {\n db.update(flowChats)\n .set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)))\n .run();\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n const row = db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : toMs(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDayClosed(chatId, day) {\n db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDelivery(chatId, delivery) {\n const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };\n db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async readTimeZone(ownerId) {\n return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n db.insert(flowPeople)\n .values({ ownerId, timeZone, updatedAt: toMs(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } })\n .run();\n },\n\n async readSideTasks(ownerId, chatId) {\n const rows = db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))))\n .all();\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async forgetSideTasks(ownerId) {\n db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();\n },\n async noteSideTask(input) {\n const asked = input.status === \"asked\";\n const doneAt = asked ? null : toMs(input.at);\n const row = db\n .insert(flowSideTasks)\n .values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` },\n })\n .returning()\n .get();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n const row = db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)))\n .get();\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n const row = db\n .insert(flowFirstMessages)\n .values({ ownerId, day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count })\n .get();\n return row!.count;\n },\n\n async close() {\n if (sqlite.open) sqlite.close();\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { index, integer, primaryKey, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n// Времена — миллисекунды с 1970 года, JSON — текстом.\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = sqliteTable(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: text(\"pending\", { mode: \"json\" }).$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: integer(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: integer(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: integer(\"updated_at\").notNull(),\n endedAt: integer(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: integer(\"paused_until\"),\n windowClosed: integer(\"window_closed\", { mode: \"boolean\" }).notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: text(\"delivery\", { mode: \"json\" }).$type<FlowPendingDelivery>(),\n placement: text(\"placement\", { mode: \"json\" }).$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = sqliteTable(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: text(\"flow\", { mode: \"json\" }).$type<AgentFlow>().notNull(),\n createdAt: integer(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = sqliteTable(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: text(\"value\", { mode: \"json\" }).notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = sqliteTable(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = sqliteTable(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = sqliteTable(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: integer(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":";;;;;;;;;;;;AAAA,SAAS,qBAAqB;;;ACA9B,OAAO,cAAc;AACrB,SAAS,KAAK,KAAK,IAAI,SAAS,QAAQ,IAAI,KAAK,IAAI,IAAI,WAAqB;AAC9E,SAAS,eAAe;AACxB,SAAS,eAAe;;;ACAxB,SAAS,OAAO,SAAS,YAAY,aAAa,YAAY;AAKvD,IAAM,YAAY;AAAA,EACvB;AAAA,EACA;AAAA,IACE,QAAQ,KAAK,SAAS,EAAE,WAAW;AAAA,IACnC,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,WAAW,KAAK,YAAY,EAAE,QAAQ;AAAA,IACtC,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,SAAS,KAAK,WAAW,EAAE,MAAM,OAAO,CAAC,EAAE,MAA2B,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAAA,IAC5F,OAAO,QAAQ,QAAQ;AAAA,IACvB,WAAW,KAAK,aAAa;AAAA,IAC7B,eAAe,KAAK,iBAAiB;AAAA,IACrC,UAAU,KAAK,WAAW,EAAE,QAAQ;AAAA,IACpC,YAAY,QAAQ,aAAa;AAAA,IACjC,YAAY,KAAK,aAAa;AAAA,IAC9B,WAAW,QAAQ,YAAY,EAAE,QAAQ;AAAA,IACzC,SAAS,QAAQ,UAAU;AAAA,IAC3B,WAAW,KAAK,YAAY,EAAE,MAAqB;AAAA,IACnD,aAAa,QAAQ,cAAc;AAAA,IACnC,cAAc,QAAQ,iBAAiB,EAAE,MAAM,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,IACnF,aAAa,KAAK,eAAe;AAAA,IACjC,UAAU,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC,EAAE,MAA2B;AAAA,IACxE,WAAW,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC,EAAE,MAAyB;AAAA,EAC1E;AAAA,EACA,CAAC,UAAU;AAAA,IACT,MAAM,oBAAoB,EAAE,GAAG,MAAM,KAAK;AAAA,IAC1C,MAAM,2BAA2B,EAAE,GAAG,MAAM,QAAQ,MAAM,WAAW,MAAM,OAAO;AAAA,IAClF,MAAM,sBAAsB,EAAE,GAAG,MAAM,OAAO;AAAA,EAChD;AACF;AAGO,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,WAAW,KAAK,YAAY,EAAE,QAAQ;AAAA,IACtC,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,IAC3B,MAAM,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC,EAAE,MAAiB,EAAE,QAAQ;AAAA,IAChE,WAAW,QAAQ,YAAY,EAAE,QAAQ;AAAA,EAC3C;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,QAAQ,MAAM,SAAS,EAAE,CAAC,CAAC;AACtE;AAGO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,IACE,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,OAAO,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC,EAAE,QAAQ;AAAA,IAC/C,WAAW,QAAQ,YAAY,EAAE,QAAQ;AAAA,IACzC,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,EAClC;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,SAAS,MAAM,OAAO,EAAE,CAAC,CAAC;AACrE;AAGO,IAAM,oBAAoB;AAAA,EAC/B;AAAA,EACA;AAAA,IACE,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,KAAK,KAAK,KAAK,EAAE,QAAQ;AAAA,IACzB,OAAO,QAAQ,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,EAC7C;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,SAAS,MAAM,GAAG,EAAE,CAAC,CAAC;AACjE;AAGO,IAAM,aAAa,YAAY,eAAe;AAAA,EACnD,SAAS,KAAK,UAAU,EAAE,WAAW;AAAA,EACrC,UAAU,KAAK,WAAW,EAAE,QAAQ;AAAA,EACpC,WAAW,QAAQ,YAAY,EAAE,QAAQ;AAC3C,CAAC;AAGM,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AAAA,IACE,SAAS,KAAK,UAAU,EAAE,QAAQ;AAAA,IAClC,QAAQ,KAAK,SAAS,EAAE,QAAQ;AAAA,IAChC,QAAQ,KAAK,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE;AAAA,IAC5C,MAAM,QAAQ,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC;AAAA,IACzC,QAAQ,QAAQ,SAAS;AAAA,EAC3B;AAAA,EACA,CAAC,UAAU,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,SAAS,MAAM,QAAQ,MAAM,MAAM,EAAE,CAAC,CAAC;AAClF;;;ADhFA,IAAM,OAAO,CAAC,QAAgB,KAAK,MAAM,GAAG;AAC5C,IAAM,QAAQ,CAAC,OAAe,IAAI,KAAK,EAAE,EAAE,YAAY;AAEvD,SAAS,SAAS,KAA8B;AAC9C,SAAO;AAAA,IACL,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,QAAQ,IAAI;AAAA,IACZ,WAAW,IAAI;AAAA,IACf,SAAS,IAAI;AAAA,IACb,SAAS,IAAI;AAAA,IACb,OAAO,IAAI,UAAU,OAAO,OAAO,MAAM,IAAI,KAAK;AAAA,IAClD,WAAW,IAAI;AAAA,IACf,eAAe,IAAI;AAAA,IACnB,UAAU,IAAI;AAAA,IACd,WAAW,MAAM,IAAI,SAAS;AAAA,IAC9B,SAAS,IAAI,YAAY,OAAO,OAAO,MAAM,IAAI,OAAO;AAAA,IACxD,WAAW,IAAI;AAAA,IACf,aAAa,IAAI,gBAAgB,OAAO,OAAO,MAAM,IAAI,WAAW;AAAA,IACpE,cAAc,IAAI;AAAA,IAClB,aAAa,IAAI;AAAA,IACjB,UAAU,IAAI;AAAA,IACd,WAAW,IAAI;AAAA,EACjB;AACF;AAGA,IAAM,aAAa,CAAC,SAAsC;AAAA,EACxD,QAAQ,IAAI;AAAA,EACZ,QAAQ,IAAI,WAAW,KAAK,OAAO,IAAI;AAAA,EACvC,MAAM,IAAI;AAAA,EACV,QAAQ,IAAI,WAAW,OAAO,OAAO,MAAM,IAAI,MAAM;AACvD;AAGA,IAAM,cAAc,CAAC,WACnB,gDAAgD,UAAU,OAAO,0CAA0C,OAAO,GAAG;AAAA,WAC5G,UAAU,OAAO,qBAAqB,UAAU,OAAO,kBAAkB,KAAK,UAAU,MAAM,CAAC;AAG1G,SAAS,eAAe,KAAoB;AAC1C,QAAM,UAAU,OAAO,QAAQ,GAAG;AAClC,MAAI,QAAQ,WAAW,EAAG,QAAO,MAAM,UAAU,OAAO;AACxD,QAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,MAAM,EAAE,MAAM,WAAW,IAAI,SAAS,EAAE,EAAE;AACtE,SAAO,WAAW,UAAU,OAAO,IAAI,IAAI,KAAK,OAAO,MAAM,CAAC,SAAS,UAAU,OAAO;AAC1F;AAGA,IAAM,aAAa,CAAC,QAClB;AAAA,EACE,IAAI,UAAU,OAAO,GAAG;AAAA,EACxB,GAAG,OAAO,UAAU,UAAU,GAAG,GAAG,UAAU,YAAY,GAAG,CAAC;AAAA,EAC9D,OAAO,UAAU,OAAO;AAAA,EACxB,GAAG,OAAO,UAAU,WAAW,GAAG,IAAI,UAAU,aAAa,GAAG,CAAC;AACnE;AAOK,SAAS,sBAAsB,UAAkB,kBAA2C;AACjG,QAAM,SAAS,IAAI,SAAS,QAAQ;AACpC,SAAO,OAAO,oBAAoB;AAClC,SAAO,OAAO,qBAAqB;AACnC,QAAM,KAAK,QAAQ,MAAM;AACzB,UAAQ,IAAI,EAAE,kBAAkB,iBAAiB,uBAAuB,CAAC;AAEzE,QAAM,UAAU,CAAC,WAAmB,GAAG,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAExG,SAAO;AAAA,IACL,MAAM,QAAQ,OAAO;AACnB,SAAG,OAAO,SAAS,EAChB,OAAO;AAAA,QACN,QAAQ,MAAM;AAAA,QACd,SAAS,MAAM;AAAA,QACf,QAAQ,MAAM;AAAA,QACd,WAAW,MAAM;AAAA,QACjB,SAAS,MAAM;AAAA,QACf,SAAS,CAAC;AAAA,QACV,UAAU,MAAM;AAAA,QAChB,GAAI,MAAM,aAAa,EAAE,WAAW,MAAM,UAAU;AAAA,QACpD,WAAW,KAAK,MAAM,EAAE;AAAA,MAC1B,CAAC,EACA,oBAAoB,EACpB,IAAI;AACP,aAAO,SAAS,QAAQ,MAAM,MAAM,CAAE;AAAA,IACxC;AAAA,IACA,MAAM,QAAQ,QAAQ;AACpB,YAAM,MAAM,QAAQ,MAAM;AAC1B,aAAO,MAAM,SAAS,GAAG,IAAI;AAAA,IAC/B;AAAA,IACA,MAAM,WAAW,QAAQ,OAAO,IAAI;AAClC,YAAM,MAAM,GACT,OAAO,SAAS,EAChB,IAAI,EAAE,WAAW,MAAM,WAAW,SAAS,MAAM,SAAS,UAAU,MAAM,UAAU,WAAW,MAAM,WAAW,WAAW,KAAK,EAAE,EAAE,CAAC,EACrI,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAClC,UAAU,EACV,IAAI;AACP,aAAO,MAAM,SAAS,GAAG,IAAI;AAAA,IAC/B;AAAA,IACA,MAAM,WAAW,QAAQ;AACvB,SAAG,OAAO,SAAS,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAAA,IAC/D;AAAA,IAEA,MAAM,WAAW,QAAQ,QAAQ;AAE/B,YAAM,MAAM,OAAO,YAAY,MAAM;AACnC,cAAM,MAAM,GAAG,OAAO,EAAE,SAAS,UAAU,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAC9G,cAAM,OAAO,OAAO,aAAa,IAAI,SAAS,MAAM;AACpD,YAAI,KAAM,IAAG,OAAO,SAAS,EAAE,IAAI,EAAE,SAAS,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAAA,MAChG,CAAC;AACD,UAAI,UAAU;AAAA,IAChB;AAAA,IACA,MAAM,YAAY,QAAQ;AAExB,YAAM,OAAO,OAAO,YAAY,MAAM;AACpC,cAAM,MAAM,GAAG,OAAO,EAAE,SAAS,UAAU,QAAQ,CAAC,EAAE,KAAK,SAAS,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAC9G,YAAI,CAAC,OAAO,IAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAC9C,WAAG,OAAO,SAAS,EAAE,IAAI,EAAE,SAAS,IAAI,QAAQ,OAAO,aAAa,EAAE,CAAC,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AACjH,eAAO,IAAI;AAAA,MACb,CAAC;AACD,aAAO,KAAK,UAAU;AAAA,IACxB;AAAA,IAEA,MAAM,OAAO,QAAQ,KAAK,SAAS;AACjC,SAAG,OAAO,SAAS,EAChB,IAAI;AAAA,QACH,OAAO,MAAM,KAAK,IAAI,EAAE,IAAI;AAAA,QAC5B,WAAW,KAAK,UAAU;AAAA,QAC1B,eAAe,KAAK,cAAc;AAAA,QAClC,YAAY;AAAA,QACZ,YAAY;AAAA,MACd,CAAC,EACA,MAAM,IAAI,GAAG,UAAU,QAAQ,MAAM,GAAG,SAAS,iBAAiB,OAAO,UAAU,QAAQ,IAAI,MAAS,CAAC,EACzG,IAAI;AAAA,IACT;AAAA,IACA,MAAM,SAAS,EAAE,KAAK,OAAO,SAAS,MAAM,GAAG;AAC7C,YAAM,QAAQ,KAAK,GAAG;AAEtB,YAAM,MAAM,GACT,OAAO,EAAE,QAAQ,UAAU,OAAO,CAAC,EACnC,KAAK,SAAS,EACd,MAAM,WAAW,KAAK,CAAC,EACvB,QAAQ,IAAI,UAAU,KAAK,GAAG,IAAI,UAAU,MAAM,CAAC,EACnD,MAAM,KAAK;AACd,YAAM,OAAO,GACV,OAAO,SAAS,EAChB,IAAI,EAAE,YAAY,OAAO,YAAY,QAAQ,QAAQ,CAAC,EACtD,MAAM,IAAI,QAAQ,UAAU,QAAQ,GAAG,GAAG,WAAW,KAAK,CAAC,CAAC,EAC5D,UAAU,EACV,IAAI;AACP,aAAO,KAAK,IAAI,QAAQ,EAAE,KAAK,CAAC,MAAM,UAAU,YAAY,KAAK,OAAQ,MAAM,KAAM,KAAK,YAAY,KAAK,QAAQ,MAAM,MAAM,CAAC;AAAA,IAClI;AAAA,IACA,MAAM,aAAa,QAAQ,OAAO;AAChC,SAAG,OAAO,SAAS,EAChB,IAAI,EAAE,YAAY,MAAM,YAAY,KAAK,CAAC,EAC1C,MAAM,IAAI,GAAG,UAAU,QAAQ,MAAM,GAAG,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,EACxE,IAAI;AAAA,IACT;AAAA,IACA,MAAM,WAAW,EAAE,QAAQ,OAAO,KAAK,QAAQ,GAAG;AAChD,YAAM,UAAU,GACb,OAAO,SAAS,EAChB,IAAI,EAAE,YAAY,KAAK,GAAG,IAAI,QAAQ,CAAC,EACvC,MAAM,IAAI,GAAG,UAAU,QAAQ,MAAM,GAAG,GAAG,UAAU,YAAY,KAAK,CAAC,CAAC,EACxE,UAAU,EAAE,QAAQ,UAAU,OAAO,CAAC,EACtC,IAAI;AACP,aAAO,QAAQ,SAAS;AAAA,IAC1B;AAAA,IAEA,MAAM,YAAY,OAAO;AACvB,YAAM,WAAW,GACd,OAAO,YAAY,EACnB,OAAO,EAAE,QAAQ,MAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,WAAW,KAAK,MAAM,EAAE,EAAE,CAAC,EAC1H,oBAAoB,EACpB,UAAU,EAAE,MAAM,aAAa,KAAK,CAAC,EACrC,IAAI;AACP,UAAI,SAAS,SAAS,EAAG,QAAO;AAChC,YAAM,QAAQ,GACX,OAAO,EAAE,MAAM,aAAa,KAAK,CAAC,EAClC,KAAK,YAAY,EACjB,MAAM,IAAI,GAAG,aAAa,QAAQ,MAAM,MAAM,GAAG,GAAG,aAAa,WAAW,MAAM,SAAS,CAAC,CAAC,EAC7F,IAAI;AACP,aAAO,OAAO,SAAS,MAAM,OAAO,SAAS;AAAA,IAC/C;AAAA,IACA,MAAM,WAAW,QAAQ,WAAW;AAClC,YAAM,MAAM,GACT,OAAO,EACP,KAAK,YAAY,EACjB,MAAM,IAAI,GAAG,aAAa,QAAQ,MAAM,GAAG,GAAG,aAAa,WAAW,SAAS,CAAC,CAAC,EACjF,IAAI;AACP,aAAO,MAAM,EAAE,GAAG,KAAK,WAAW,MAAM,IAAI,SAAS,EAAE,IAAI;AAAA,IAC7D;AAAA,IAEA,MAAM,kBAAkB,EAAE,OAAO,GAAG;AAClC,YAAM,OAAO,GACV,OAAO,EAAE,WAAW,UAAU,WAAW,SAAS,UAAU,SAAS,OAAO,cAAsB,CAAC,EACnG,KAAK,SAAS,EACd,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAClC,QAAQ,UAAU,WAAW,UAAU,OAAO,EAC9C,IAAI;AACP,aAAO,KAAK,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,OAAO,OAAO,IAAI,KAAK,EAAE,EAAE,EAAE,KAAK,iBAAiB;AAAA,IACzF;AAAA,IACA,MAAM,cAAc,EAAE,QAAQ,iBAAiB,QAAQ,GAAG;AACxD,UAAI,SAAS,WAAW,EAAG,QAAO,CAAC;AACnC,YAAM,OAAO,GACV,OAAO;AAAA,QACN,QAAQ,UAAU;AAAA,QAClB,SAAS,UAAU;AAAA,QACnB,QAAQ,UAAU;AAAA,QAClB,WAAW,UAAU;AAAA,QACrB,SAAS,UAAU;AAAA,QACnB,SAAS,UAAU;AAAA,QACnB,WAAW,UAAU;AAAA,MACvB,CAAC,EACA,KAAK,SAAS,EACd;AAAA,QACC;AAAA,UACE,GAAG,UAAU,QAAQ,MAAM;AAAA,UAC3B,oBAAoB,SAAY,SAAY,GAAG,UAAU,WAAW,eAAe;AAAA,UACnF,YAAY,SAAY,SAAY,QAAQ,UAAU,QAAQ,OAAO;AAAA,QACvE;AAAA,MACF,EACC,QAAQ,IAAI,UAAU,MAAM,CAAC,EAC7B,IAAI;AACP,aAAO,KAAK,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,SAAS,IAAI,YAAY,OAAO,OAAO,MAAM,IAAI,OAAO,EAAE,EAAE;AAAA,IAClG;AAAA,IACA,MAAM,aAAa,OAAO;AACxB,YAAM,SAAS,eAAe,KAAK;AACnC,UAAI,MAAM,SAAS,WAAW,EAAG,QAAO,EAAE,OAAO,CAAC,EAAE;AACpD,YAAM,cAAc;AAAA,QAClB,GAAG,UAAU,QAAQ,MAAM,MAAM;AAAA,QACjC,GAAG,UAAU,WAAW,MAAM,WAAW;AAAA,QACzC,MAAM,YAAY,SAAY,SAAY,QAAQ,UAAU,QAAQ,MAAM,OAAO;AAAA,MACnF;AACA,YAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,UAAU,OAAO,CAAC,EAAE,KAAK,SAAS,EAAE,MAAM,WAAW,EAAE,QAAQ,IAAI,UAAU,MAAM,CAAC,EAAE,MAAM,MAAM,KAAK;AACzI,YAAM,QAAQ,GACX,OAAO,SAAS,EAChB,IAAI,EAAE,WAAW,MAAM,aAAa,SAAS,eAAe,MAAM,GAAG,GAAG,SAAS,YAAY,MAAM,GAAG,WAAW,KAAK,MAAM,EAAE,EAAE,CAAC,EACjI,MAAM,IAAI,QAAQ,UAAU,QAAQ,KAAK,GAAG,WAAW,CAAC,EACxD,UAAU,EAAE,QAAQ,UAAU,OAAO,CAAC,EACtC,IAAI;AACP,aAAO,EAAE,OAAO,MAAM,IAAI,CAAC,QAAQ,IAAI,MAAM,EAAE;AAAA,IACjD;AAAA,IAEA,MAAM,SAAS,SAAS;AACtB,YAAM,OAAO,GAAG,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,GAAG,gBAAgB,SAAS,OAAO,CAAC,EAAE,IAAI;AAC/F,aAAO,KACJ,IAAI,CAAC,SAAS,EAAE,SAAS,IAAI,SAAS,OAAO,IAAI,OAAO,WAAW,MAAM,IAAI,SAAS,GAAG,QAAQ,IAAI,QAAQ,QAAQ,IAAI,OAAO,EAAE,EAClI,KAAK,CAAC,MAAM,UAAU,YAAY,KAAK,SAAS,MAAM,OAAO,CAAC;AAAA,IACnE;AAAA,IACA,MAAM,eAAe,OAAO;AAC1B,YAAM,QAAQ,EAAE,OAAO,MAAM,OAAO,WAAW,KAAK,MAAM,EAAE,GAAG,QAAQ,MAAM,QAAQ,QAAQ,MAAM,OAAO;AAC1G,SAAG,OAAO,eAAe,EACtB,OAAO,EAAE,SAAS,MAAM,SAAS,SAAS,MAAM,SAAS,GAAG,MAAM,CAAC,EACnE,mBAAmB,EAAE,QAAQ,CAAC,gBAAgB,SAAS,gBAAgB,OAAO,GAAG,KAAK,MAAM,CAAC,EAC7F,IAAI;AAAA,IACT;AAAA,IACA,MAAM,WAAW,SAAS,SAAS;AACjC,SAAG,OAAO,eAAe,EACtB,MAAM,IAAI,GAAG,gBAAgB,SAAS,OAAO,GAAG,YAAY,QAAQ,SAAY,GAAG,gBAAgB,SAAS,OAAO,CAAC,CAAC,EACrH,IAAI;AAAA,IACT;AAAA,IAEA,MAAM,QAAQ,QAAQ,EAAE,QAAQ,GAAG,GAAG;AACpC,SAAG,OAAO,SAAS,EAChB,IAAI,EAAE,SAAS,KAAK,EAAE,GAAG,WAAW,QAAQ,OAAO,MAAM,WAAW,MAAM,eAAe,MAAM,YAAY,MAAM,YAAY,KAAK,CAAC,EACnI,MAAM,IAAI,GAAG,UAAU,QAAQ,MAAM,GAAG,OAAO,UAAU,OAAO,CAAC,CAAC,EAClE,IAAI;AACP,YAAM,MAAM,QAAQ,MAAM;AAC1B,aAAO,MAAM,SAAS,GAAG,IAAI;AAAA,IAC/B;AAAA,IACA,MAAM,SAAS,QAAQ,OAAO;AAC5B,YAAM,MAAM,GACT,OAAO,SAAS,EAChB,IAAI,EAAE,aAAa,UAAU,OAAO,OAAO,KAAK,KAAK,EAAE,CAAC,EACxD,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAClC,UAAU,EACV,IAAI;AACP,aAAO,MAAM,SAAS,GAAG,IAAI;AAAA,IAC/B;AAAA,IACA,MAAM,gBAAgB,QAAQ,QAAQ;AACpC,SAAG,OAAO,SAAS,EAAE,IAAI,EAAE,cAAc,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAAA,IAC7F;AAAA,IACA,MAAM,aAAa,QAAQ,KAAK;AAC9B,SAAG,OAAO,SAAS,EAAE,IAAI,EAAE,aAAa,IAAI,CAAC,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAAA,IACzF;AAAA,IACA,MAAM,YAAY,QAAQ,UAAU;AAClC,YAAM,QAAQ,aAAa,OAAO,OAAO,EAAE,GAAG,UAAU,SAAS,MAAM,KAAK,SAAS,OAAO,CAAC,EAAE;AAC/F,SAAG,OAAO,SAAS,EAAE,IAAI,EAAE,UAAU,MAAM,CAAC,EAAE,MAAM,GAAG,UAAU,QAAQ,MAAM,CAAC,EAAE,IAAI;AAAA,IACxF;AAAA,IAEA,MAAM,aAAa,SAAS;AAC1B,aAAO,GAAG,OAAO,EAAE,UAAU,WAAW,SAAS,CAAC,EAAE,KAAK,UAAU,EAAE,MAAM,GAAG,WAAW,SAAS,OAAO,CAAC,EAAE,IAAI,GAAG,YAAY;AAAA,IACjI;AAAA,IACA,MAAM,cAAc,SAAS,UAAU,IAAI;AACzC,SAAG,OAAO,UAAU,EACjB,OAAO,EAAE,SAAS,UAAU,WAAW,KAAK,EAAE,EAAE,CAAC,EACjD,mBAAmB,EAAE,QAAQ,WAAW,SAAS,KAAK,EAAE,UAAU,WAAW,KAAK,EAAE,EAAE,EAAE,CAAC,EACzF,IAAI;AAAA,IACT;AAAA,IAEA,MAAM,cAAc,SAAS,QAAQ;AACnC,YAAM,OAAO,GACV,OAAO,EACP,KAAK,aAAa,EAClB,MAAM,IAAI,GAAG,cAAc,SAAS,OAAO,GAAG,GAAG,GAAG,cAAc,QAAQ,EAAE,GAAG,GAAG,cAAc,QAAQ,MAAM,CAAC,CAAC,CAAC,EACjH,IAAI;AACP,aAAO,KAAK,IAAI,UAAU,EAAE,KAAK,CAAC,MAAM,UAAU,YAAY,KAAK,QAAQ,MAAM,MAAM,CAAC;AAAA,IAC1F;AAAA,IACA,MAAM,gBAAgB,SAAS;AAC7B,SAAG,OAAO,aAAa,EAAE,MAAM,GAAG,cAAc,SAAS,OAAO,CAAC,EAAE,IAAI;AAAA,IACzE;AAAA,IACA,MAAM,aAAa,OAAO;AACxB,YAAM,QAAQ,MAAM,WAAW;AAC/B,YAAM,SAAS,QAAQ,OAAO,KAAK,MAAM,EAAE;AAC3C,YAAM,MAAM,GACT,OAAO,aAAa,EACpB,OAAO,EAAE,SAAS,MAAM,SAAS,QAAQ,MAAM,QAAQ,QAAQ,MAAM,UAAU,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO,CAAC,EAChH,mBAAmB;AAAA,QAClB,QAAQ,CAAC,cAAc,SAAS,cAAc,QAAQ,cAAc,MAAM;AAAA,QAC1E,KAAK,QAAQ,EAAE,MAAM,MAAM,cAAc,IAAI,OAAO,IAAI,EAAE,QAAQ,eAAe,cAAc,MAAM,KAAK,MAAM,IAAI;AAAA,MACtH,CAAC,EACA,UAAU,EACV,IAAI;AACP,aAAO,WAAW,GAAI;AAAA,IACxB;AAAA,IAEA,MAAM,mBAAmB,SAAS,KAAK;AACrC,YAAM,MAAM,GACT,OAAO,EAAE,OAAO,kBAAkB,MAAM,CAAC,EACzC,KAAK,iBAAiB,EACtB,MAAM,IAAI,GAAG,kBAAkB,SAAS,OAAO,GAAG,GAAG,kBAAkB,KAAK,GAAG,CAAC,CAAC,EACjF,IAAI;AACP,aAAO,KAAK,SAAS;AAAA,IACvB;AAAA,IACA,MAAM,gBAAgB,SAAS,KAAK;AAClC,YAAM,MAAM,GACT,OAAO,iBAAiB,EACxB,OAAO,EAAE,SAAS,KAAK,OAAO,EAAE,CAAC,EACjC,mBAAmB,EAAE,QAAQ,CAAC,kBAAkB,SAAS,kBAAkB,GAAG,GAAG,KAAK,EAAE,OAAO,MAAM,kBAAkB,KAAK,OAAO,EAAE,CAAC,EACtI,UAAU,EAAE,OAAO,kBAAkB,MAAM,CAAC,EAC5C,IAAI;AACP,aAAO,IAAK;AAAA,IACd;AAAA,IAEA,MAAM,QAAQ;AACZ,UAAI,OAAO,KAAM,QAAO,MAAM;AAAA,IAChC;AAAA,EACF;AACF;;;ADrWA,IAAM,oBAAoB,cAAc,IAAI,IAAI,qBAAqB,YAAY,GAAG,CAAC;AAM9E,SAAS,gBAAgB,UAAmC;AACjE,SAAO,sBAAsB,UAAU,iBAAiB;AAC1D;","names":[]}
@@ -34,6 +34,8 @@ export interface FlowEditorLabels {
34
34
  readOnly: string;
35
35
  readOnlyHint: string;
36
36
  migrate: string;
37
+ /** Меню «⋯» шапки на узком экране: подпись кнопки. */
38
+ more: string;
37
39
  };
38
40
  board: {
39
41
  anyState: string;
@@ -23,7 +23,7 @@ export type FlowDialog = "json" | "migrate" | null;
23
23
  /**
24
24
  * Перевод идущих чатов на текущую версию. Данные и действие даёт приложение — редактор не знает, где сервер:
25
25
  * `chatsByState` — чаты на прежних версиях (`flow.chatsByState`), `migrate` — перевод с картой удалённых состояний
26
- * (`flow.migrateChats`).
26
+ * (`flow.migrateChats`). Отбор чатов в обоих вызовах один и тот же: окно показывает ровно тех, кого переведёт.
27
27
  */
28
28
  export interface FlowMigration {
29
29
  chatsByState(): Promise<ChatsInState[]>;
@@ -398,7 +398,41 @@ export interface FlowFinish {
398
398
  run: FlowRun;
399
399
  at: Date;
400
400
  }
401
- export interface MigrateChatsInput {
401
+ /** Чат сценария, который отбор (`filter`) решает считать и переводить или нет. */
402
+ export interface FlowChatCandidate {
403
+ chatId: string;
404
+ ownerId: string;
405
+ flowId: string;
406
+ /** Версия, на которой чат сейчас. */
407
+ versionId: string;
408
+ stateId: string;
409
+ /** Разговор закончился (`flow.end`, конец сценария, срок вышел). */
410
+ ended: boolean;
411
+ endReason: FlowEndReason | null;
412
+ /** Тестовый чат редактора (`FlowTestChat`, создан с `flowTestChatData()`). */
413
+ test: boolean;
414
+ /** Данные чата (`Chat.data`): пометки приложения. Чата уже нет в службе — пусто. */
415
+ data: Record<string, unknown>;
416
+ }
417
+ /**
418
+ * Какие чаты сценария считать и переводить. Одинаково в `chatsByState` и `migrateChats`: что посчитали, то и переведут.
419
+ * По умолчанию закончившиеся разговоры и тестовые чаты редактора не берутся.
420
+ */
421
+ export interface FlowChatSelection {
422
+ /** Только эти чаты. */
423
+ chatIds?: string[];
424
+ /** Свой отбор приложения: `false` — чат не считать и не переводить. */
425
+ filter?: (chat: FlowChatCandidate) => boolean | Promise<boolean>;
426
+ /** Брать и закончившиеся разговоры. */
427
+ includeEnded?: boolean;
428
+ /** Брать и тестовые чаты редактора. */
429
+ includeTestChats?: boolean;
430
+ }
431
+ export interface ChatsByStateOptions extends FlowChatSelection {
432
+ /** Без чатов, которые уже на этой версии. */
433
+ exceptVersionId?: string;
434
+ }
435
+ export interface MigrateChatsInput extends FlowChatSelection {
402
436
  flowId: string;
403
437
  toVersionId: string;
404
438
  /** Куда перевести чаты из состояний, которых нет в новой версии: id старого → id нового. */
@@ -411,10 +445,8 @@ export interface MigrateChatsInput {
411
445
  }
412
446
  export interface ChatMigrator {
413
447
  /** Сколько чатов в каких состояниях; `exceptVersionId` — без чатов, которые уже на этой версии. */
414
- chatsByState(flowId: string, options?: {
415
- exceptVersionId?: string;
416
- }): Promise<ChatsInState[]>;
417
- /** Переводит пачками; чаты применят перевод в начале своего следующего хода. */
448
+ chatsByState(flowId: string, options?: ChatsByStateOptions): Promise<ChatsInState[]>;
449
+ /** Переводит пачками отобранные чаты; они применят перевод в начале своего следующего хода. */
418
450
  migrateChats(input: MigrateChatsInput): Promise<{
419
451
  moved: number;
420
452
  }>;
@@ -15,6 +15,11 @@ export interface ChatMigratorDeps {
15
15
  flow: AgentFlow;
16
16
  map: StateMap;
17
17
  }): Promise<void>;
18
+ /** Данные чата (`Chat.data`) для отбора; чата нет в службе — `{}`. */
19
+ chatData(chat: {
20
+ ownerId: string;
21
+ chatId: string;
22
+ }): Promise<Record<string, unknown>>;
18
23
  }
19
24
  /** Перевод чатов на новую версию пачками: версия меняется сразу, состояние — в начале следующего хода чата. */
20
25
  export declare function createChatMigrator(deps: ChatMigratorDeps): ChatMigrator;
@@ -130,11 +130,22 @@ export interface ChatsInState {
130
130
  stateId: string;
131
131
  chats: number;
132
132
  }
133
+ /** Чат сценария для отбора при переводе на новую версию. */
134
+ export type FlowChatSummary = Pick<FlowChatRecord, "chatId" | "ownerId" | "flowId" | "versionId" | "stateId" | "endedAt" | "endReason">;
135
+ export interface ListFlowChatsInput {
136
+ flowId: string;
137
+ /** Без чатов, которые уже на этой версии. */
138
+ exceptVersionId?: string;
139
+ /** Только эти чаты. */
140
+ chatIds?: string[];
141
+ }
133
142
  export interface MigrateBatchInput {
134
143
  flowId: string;
135
144
  toVersionId: string;
136
145
  /** Куда перевести чаты из удалённых состояний. */
137
146
  map: StateMap;
147
+ /** Только эти чаты; нет — любые чаты сценария не на `toVersionId`. */
148
+ chatIds?: string[];
138
149
  limit: number;
139
150
  at: string;
140
151
  }
@@ -205,6 +216,8 @@ export interface FlowStore {
205
216
  countChatsByState(input: {
206
217
  flowId: string;
207
218
  }): Promise<ChatsInState[]>;
219
+ /** Чаты сценария по порядку id: для отбора перед подсчётом и переводом. */
220
+ listFlowChats(input: ListFlowChatsInput): Promise<FlowChatSummary[]>;
208
221
  /** Перевести до `limit` чатов на версию `toVersionId`: версия меняется сразу, перевод состояния — ожидающим `migrate`. */
209
222
  migrateBatch(input: MigrateBatchInput): Promise<{
210
223
  moved: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ykurilov/agent-chat-flows",
3
- "version": "0.55.0",
3
+ "version": "0.57.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -56,13 +56,13 @@
56
56
  "ai": "7.0.122",
57
57
  "drizzle-orm": "^0.45.3",
58
58
  "zod": "^4.6.5",
59
- "@ykurilov/agent-chat-core": "0.55.0"
59
+ "@ykurilov/agent-chat-core": "0.57.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "better-sqlite3": "^13.0.3",
63
63
  "pg": "^8.23.0",
64
64
  "react": "^19.3.0",
65
- "@ykurilov/agent-chat-react": "^0.55.0"
65
+ "@ykurilov/agent-chat-react": "^0.57.0"
66
66
  },
67
67
  "peerDependenciesMeta": {
68
68
  "@ykurilov/agent-chat-react": {
@@ -90,7 +90,7 @@
90
90
  "pg": "^8.23.0",
91
91
  "react": "^19.3.0",
92
92
  "tsx": "4.23.15",
93
- "@ykurilov/agent-chat-react": "0.55.0"
93
+ "@ykurilov/agent-chat-react": "0.57.0"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "node ../../scripts/build-package.mjs",