@ykurilov/agent-chat-flows 0.56.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/postgres.js CHANGED
@@ -269,11 +269,37 @@ function createPostgresFlowStore(pool, migrationsFolder) {
269
269
  const rows = await db.select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql`count(*)::int` }).from(flowChats).where(eq(flowChats.flowId, flowId)).groupBy(flowChats.versionId, flowChats.stateId);
270
270
  return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);
271
271
  },
272
+ async listFlowChats(input) {
273
+ await ready;
274
+ const { flowId, exceptVersionId, chatIds } = withoutNul(input);
275
+ if (chatIds?.length === 0) return [];
276
+ const rows = await db.select({
277
+ chatId: flowChats.chatId,
278
+ ownerId: flowChats.ownerId,
279
+ flowId: flowChats.flowId,
280
+ versionId: flowChats.versionId,
281
+ stateId: flowChats.stateId,
282
+ endedAt: flowChats.endedAt,
283
+ endReason: flowChats.endReason
284
+ }).from(flowChats).where(
285
+ and(
286
+ eq(flowChats.flowId, flowId),
287
+ exceptVersionId === void 0 ? void 0 : ne(flowChats.versionId, exceptVersionId),
288
+ chatIds === void 0 ? void 0 : inArray(flowChats.chatId, chatIds)
289
+ )
290
+ ).orderBy(inCodeOrder(flowChats.chatId));
291
+ return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));
292
+ },
272
293
  async migrateBatch(input) {
273
294
  await ready;
274
295
  const clean = withoutNul(input);
275
296
  const change = migratePending(clean);
276
- const notYetMoved = and(eq(flowChats.flowId, clean.flowId), ne(flowChats.versionId, clean.toVersionId));
297
+ if (clean.chatIds?.length === 0) return { moved: [] };
298
+ const notYetMoved = and(
299
+ eq(flowChats.flowId, clean.flowId),
300
+ ne(flowChats.versionId, clean.toVersionId),
301
+ clean.chatIds === void 0 ? void 0 : inArray(flowChats.chatId, clean.chatIds)
302
+ );
277
303
  const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(inCodeOrder(flowChats.chatId)).limit(clean.limit).for("update", { skipLocked: true });
278
304
  const moved = await db.update(flowChats).set({ versionId: clean.toVersionId, stateId: mappedStateSql(clean.map), pending: pendingWith(change), updatedAt: new Date(clean.at) }).where(and(inArray(flowChats.chatId, batch), notYetMoved)).returning({ chatId: flowChats.chatId });
279
305
  return { moved: moved.map((row) => row.chatId) };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/postgres.ts","../src/store/postgres.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresFlowStore, type PostgresFlowStore } from \"./store/postgres\";\n\nexport type { PostgresFlowStore } from \"./store/postgres\";\nexport { FLOWS_MIGRATIONS_TABLE, FLOWS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/**\n * Сценарии в PostgreSQL на пуле приложения (схема `agent_chat`, та же база, что у хранилища чатов).\n * Таблицы приводятся к последней миграции при первом обращении. Пул закрывает приложение.\n */\nexport function postgresFlowStore(pool: Pool): PostgresFlowStore {\n return createPostgresFlowStore(pool, MIGRATIONS_FOLDER);\n}\n","import { and, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport type { Pool } from \"pg\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { byVersionAndState, compareText, migratePending, withoutNul } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATION_LOCK_KEY, FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./postgres-schema\";\n\n/** Хранилище, у которого можно дождаться миграции перед закрытием пула приложения. */\nexport type PostgresFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toIso = (date: Date) => date.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/** JSON параметром запроса: текст, приведённый к `jsonb` в самой базе. */\nconst jsonb = (value: unknown): SQL => sql`${JSON.stringify(value)}::jsonb`;\n\n/**\n * Дописать изменение в конец `pending`, если такого ключа там ещё нет. Условие — в том же `update`: вторая копия ждёт\n * запись и перепроверяет его уже на новой версии строки.\n */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when ${flowChats.pending} @> ${jsonb([{ key: change.key }])} then ${keptWith(change)}\n else ${flowChats.pending} || ${jsonb([change])} end`;\n\n/** Тот же ключ: прежнее остаётся, но `appliedAt` перехода дописывается к ожидающему без него (как `pendingAdded`). */\nfunction keptWith(change: PendingFlowChange): SQL {\n if (change.kind !== \"transition\" || change.appliedAt === undefined) return sql`${flowChats.pending}`;\n return sql`(select jsonb_agg(case when item->>'key' = ${change.key} and item->'appliedAt' is null and item->>'kind' = 'transition'\n then item || jsonb_build_object('appliedAt', ${change.appliedAt}::text) else item end order by place)\n from jsonb_array_elements(${flowChats.pending}) with ordinality as list(item, place))`;\n}\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: Date) =>\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/** Порядок по кодам символов, как у SQLite и памяти, а не по языку базы. */\nconst inCodeOrder = (column: typeof flowChats.chatId) => sql`${column} collate \"C\"`;\n\n/**\n * Приводит таблицы сценариев к последней миграции. Идёт на одном соединении под блокировкой базы:\n * если несколько серверов стартуют сразу, второй ждёт первого и видит применённые миграции.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"select pg_advisory_lock($1)\", [FLOWS_MIGRATION_LOCK_KEY]);\n try {\n await migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE });\n } finally {\n await client.query(\"select pg_advisory_unlock($1)\", [FLOWS_MIGRATION_LOCK_KEY]);\n }\n } finally {\n client.release();\n }\n}\n\n/**\n * Сценарии в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция идёт при первом обращении, каждая операция\n * её дожидается. Копии сервера делят таймеры и перевод чатов через `for update skip locked`: строку, которую уже\n * берёт другая копия, эта пропускает. Закрытие пул не закрывает: он принадлежит приложению.\n */\nexport function createPostgresFlowStore(pool: Pool, migrationsFolder: string): PostgresFlowStore {\n const db = drizzle(pool);\n const ready = migrateFlowsToLatest(pool, migrationsFolder);\n // Ошибка миграции придёт в каждую операцию; без этого она всплыла бы ещё и как необработанная.\n ready.catch(() => {});\n\n async function getChat(chatId: string): Promise<ChatRow | undefined> {\n await ready;\n const [row] = await db.select().from(flowChats).where(eq(flowChats.chatId, chatId));\n return row;\n }\n\n return {\n async pinChat(input) {\n await ready;\n const clean = withoutNul(input);\n await db\n .insert(flowChats)\n .values({\n chatId: clean.chatId,\n ownerId: clean.ownerId,\n flowId: clean.flowId,\n versionId: clean.versionId,\n stateId: clean.stateId,\n pending: [],\n timeZone: clean.timeZone,\n ...(clean.placement && { placement: clean.placement }),\n updatedAt: new Date(clean.at),\n })\n .onConflictDoNothing();\n return toRecord((await getChat(clean.chatId))!);\n },\n async getChat(chatId) {\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n await ready;\n const clean = withoutNul(patch);\n const [row] = await db\n .update(flowChats)\n .set({ versionId: clean.versionId, stateId: clean.stateId, timeZone: clean.timeZone, placement: clean.placement, updatedAt: new Date(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n await ready;\n await db.delete(flowChats).where(eq(flowChats.chatId, chatId));\n },\n\n async addPending(chatId, change) {\n await ready;\n await db.update(flowChats).set({ pending: pendingWith(withoutNul(change)) }).where(eq(flowChats.chatId, chatId));\n },\n async takePending(chatId) {\n await ready;\n // Старое значение — из строки, захваченной `for update`: вторая копия ждёт её и получает уже пустой список\n // (кроме переходов, применённых в срок: они остаются).\n const result = await db.execute<{ pending: PendingFlowChange[] }>(sql`\n with taken as (select chat_id, pending from ${flowChats} where chat_id = ${chatId} for update)\n update ${flowChats} set pending = (\n select coalesce(jsonb_agg(item order by place), '[]'::jsonb)\n from jsonb_array_elements(taken.pending) with ordinality as list(item, place)\n where item->>'kind' = 'transition' and item->'appliedAt' is not null\n ) from taken\n where ${flowChats.chatId} = taken.chat_id\n returning taken.pending`);\n return result.rows[0]?.pending ?? [];\n },\n\n async setDue(chatId, due, options) {\n await ready;\n await db\n .update(flowChats)\n .set({\n dueAt: due ? new Date(due.at) : null,\n dueExitId: due ? withoutNul(due.exitId) : null,\n dueBranchHead: due ? withoutNul(due.branchHead) : null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined));\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n await ready;\n const nowDate = new Date(now);\n // Строки, которые сейчас берёт другая копия, пропускаются; условие повторено снаружи — его база перепроверит\n // на новой версии строки, если та успела измениться.\n // Выборка — отдельным шагом `with`: подзапрос с пределом внутри `in (…)` база может перечитать и взять больше.\n const due = db.$with(\"due\").as(\n db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowDate))\n .orderBy(flowChats.dueAt, inCodeOrder(flowChats.chatId))\n .limit(limit)\n .for(\"update\", { skipLocked: true }),\n );\n const rows = await db\n .with(due)\n .update(flowChats)\n .set({ leaseOwner: withoutNul(owner), leaseUntil: new Date(nowDate.getTime() + leaseMs) })\n .where(and(inArray(flowChats.chatId, db.select({ chatId: due.chatId }).from(due)), dueAndFree(nowDate)))\n .returning();\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 await ready;\n await db\n .update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)));\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n await ready;\n const renewed = await db\n .update(flowChats)\n .set({ leaseUntil: new Date(new Date(now).getTime() + leaseMs) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, withoutNul(owner))))\n .returning({ chatId: flowChats.chatId });\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n await ready;\n const clean = withoutNul(input);\n const inserted = await db\n .insert(flowVersions)\n .values({ flowId: clean.flowId, versionId: clean.versionId, hash: clean.hash, flow: clean.flow, createdAt: new Date(clean.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash });\n if (inserted.length > 0) return \"new\";\n const [saved] = await db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, clean.flowId), eq(flowVersions.versionId, clean.versionId)));\n return saved?.hash === clean.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n await ready;\n const [row] = await db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)));\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n await ready;\n const rows = await db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)::int` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId);\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async migrateBatch(input) {\n await ready;\n const clean = withoutNul(input);\n const change = migratePending(clean);\n const notYetMoved = and(eq(flowChats.flowId, clean.flowId), ne(flowChats.versionId, clean.toVersionId));\n const batch = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(notYetMoved)\n .orderBy(inCodeOrder(flowChats.chatId))\n .limit(clean.limit)\n .for(\"update\", { skipLocked: true });\n const moved = await db\n .update(flowChats)\n .set({ versionId: clean.toVersionId, stateId: mappedStateSql(clean.map), pending: pendingWith(change), updatedAt: new Date(clean.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId });\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n await ready;\n // Значение читаем текстом и разбираем сами: иначе строка «007» из jsonb вернулась бы числом 7.\n const rows = await db\n .select({\n fieldId: flowClientCards.fieldId,\n value: sql<string>`${flowClientCards.value}::text`,\n updatedAt: flowClientCards.updatedAt,\n chatId: flowClientCards.chatId,\n flowId: flowClientCards.flowId,\n })\n .from(flowClientCards)\n .where(eq(flowClientCards.ownerId, ownerId));\n return rows\n .map((row) => ({ ...row, value: JSON.parse(row.value) as unknown, updatedAt: toIso(row.updatedAt) }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n await ready;\n const clean = withoutNul(input);\n const entry = { value: jsonb(clean.value), updatedAt: new Date(clean.at), chatId: clean.chatId, flowId: clean.flowId };\n await db\n .insert(flowClientCards)\n .values({ ownerId: clean.ownerId, fieldId: clean.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry });\n },\n async forgetCard(ownerId, fieldId) {\n await ready;\n await db\n .delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)));\n },\n\n async endChat(chatId, { reason, at }) {\n await ready;\n await db\n .update(flowChats)\n .set({ endedAt: new Date(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : new Date(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n await ready;\n await db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId));\n },\n async setDayClosed(chatId, day) {\n await ready;\n await db.update(flowChats).set({ dayClosedOn: day === null ? null : withoutNul(day) }).where(eq(flowChats.chatId, chatId));\n },\n async setDelivery(chatId, delivery) {\n await ready;\n const clean = delivery === null ? null : withoutNul({ ...delivery, retryAt: toIso(new Date(delivery.retryAt)) });\n await db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId));\n },\n\n async readTimeZone(ownerId) {\n await ready;\n const [row] = await db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId));\n return row?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n await ready;\n const clean = withoutNul({ ownerId, timeZone });\n await db\n .insert(flowPeople)\n .values({ ...clean, updatedAt: new Date(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone: clean.timeZone, updatedAt: new Date(at) } });\n },\n\n async forgetSideTasks(ownerId) {\n await ready;\n await db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId));\n },\n async readSideTasks(ownerId, chatId) {\n await ready;\n const rows = await db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))));\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async noteSideTask(input) {\n await ready;\n const clean = withoutNul(input);\n const asked = clean.status === \"asked\";\n const doneAt = asked ? null : new Date(clean.at);\n const [row] = await db\n .insert(flowSideTasks)\n .values({ ownerId: clean.ownerId, taskId: clean.taskId, chatId: clean.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}, excluded.done_at)` },\n })\n .returning();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n await ready;\n const [row] = await db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)));\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n await ready;\n const [row] = await db\n .insert(flowFirstMessages)\n .values({ ownerId: withoutNul(ownerId), day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count });\n return row!.count;\n },\n\n async close() {\n // Дожидаемся миграции: пул приложения можно закрывать сразу после.\n await ready.catch(() => {});\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 { boolean, index, integer, jsonb, pgSchema, primaryKey, text, timestamp } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = agentChatSchema.table(\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: jsonb(\"pending\").$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: moment(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: moment(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: moment(\"updated_at\").notNull(),\n endedAt: moment(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: moment(\"paused_until\"),\n windowClosed: boolean(\"window_closed\").notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: jsonb(\"delivery\").$type<FlowPendingDelivery>(),\n placement: jsonb(\"placement\").$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 = agentChatSchema.table(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: jsonb(\"flow\").$type<AgentFlow>().notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = agentChatSchema.table(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: jsonb(\"value\").notNull(),\n updatedAt: moment(\"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 = agentChatSchema.table(\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 = agentChatSchema.table(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = agentChatSchema.table(\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: moment(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":["jsonb"]}
1
+ {"version":3,"sources":["../src/postgres.ts","../src/store/postgres.ts","../src/store/postgres-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport type { Pool } from \"pg\";\nimport { createPostgresFlowStore, type PostgresFlowStore } from \"./store/postgres\";\n\nexport type { PostgresFlowStore } from \"./store/postgres\";\nexport { FLOWS_MIGRATIONS_TABLE, FLOWS_MIGRATION_LOCK_KEY } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/postgres-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/postgres`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/postgres\", import.meta.url));\n\n/**\n * Сценарии в PostgreSQL на пуле приложения (схема `agent_chat`, та же база, что у хранилища чатов).\n * Таблицы приводятся к последней миграции при первом обращении. Пул закрывает приложение.\n */\nexport function postgresFlowStore(pool: Pool): PostgresFlowStore {\n return createPostgresFlowStore(pool, MIGRATIONS_FOLDER);\n}\n","import { and, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/node-postgres\";\nimport { migrate } from \"drizzle-orm/node-postgres/migrator\";\nimport type { Pool } from \"pg\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { byVersionAndState, compareText, migratePending, withoutNul } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATION_LOCK_KEY, FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./postgres-schema\";\n\n/** Хранилище, у которого можно дождаться миграции перед закрытием пула приложения. */\nexport type PostgresFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toIso = (date: Date) => date.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/** JSON параметром запроса: текст, приведённый к `jsonb` в самой базе. */\nconst jsonb = (value: unknown): SQL => sql`${JSON.stringify(value)}::jsonb`;\n\n/**\n * Дописать изменение в конец `pending`, если такого ключа там ещё нет. Условие — в том же `update`: вторая копия ждёт\n * запись и перепроверяет его уже на новой версии строки.\n */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when ${flowChats.pending} @> ${jsonb([{ key: change.key }])} then ${keptWith(change)}\n else ${flowChats.pending} || ${jsonb([change])} end`;\n\n/** Тот же ключ: прежнее остаётся, но `appliedAt` перехода дописывается к ожидающему без него (как `pendingAdded`). */\nfunction keptWith(change: PendingFlowChange): SQL {\n if (change.kind !== \"transition\" || change.appliedAt === undefined) return sql`${flowChats.pending}`;\n return sql`(select jsonb_agg(case when item->>'key' = ${change.key} and item->'appliedAt' is null and item->>'kind' = 'transition'\n then item || jsonb_build_object('appliedAt', ${change.appliedAt}::text) else item end order by place)\n from jsonb_array_elements(${flowChats.pending}) with ordinality as list(item, place))`;\n}\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: Date) =>\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/** Порядок по кодам символов, как у SQLite и памяти, а не по языку базы. */\nconst inCodeOrder = (column: typeof flowChats.chatId) => sql`${column} collate \"C\"`;\n\n/**\n * Приводит таблицы сценариев к последней миграции. Идёт на одном соединении под блокировкой базы:\n * если несколько серверов стартуют сразу, второй ждёт первого и видит применённые миграции.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"select pg_advisory_lock($1)\", [FLOWS_MIGRATION_LOCK_KEY]);\n try {\n await migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE });\n } finally {\n await client.query(\"select pg_advisory_unlock($1)\", [FLOWS_MIGRATION_LOCK_KEY]);\n }\n } finally {\n client.release();\n }\n}\n\n/**\n * Сценарии в PostgreSQL (схема `agent_chat`) на пуле приложения. Миграция идёт при первом обращении, каждая операция\n * её дожидается. Копии сервера делят таймеры и перевод чатов через `for update skip locked`: строку, которую уже\n * берёт другая копия, эта пропускает. Закрытие пул не закрывает: он принадлежит приложению.\n */\nexport function createPostgresFlowStore(pool: Pool, migrationsFolder: string): PostgresFlowStore {\n const db = drizzle(pool);\n const ready = migrateFlowsToLatest(pool, migrationsFolder);\n // Ошибка миграции придёт в каждую операцию; без этого она всплыла бы ещё и как необработанная.\n ready.catch(() => {});\n\n async function getChat(chatId: string): Promise<ChatRow | undefined> {\n await ready;\n const [row] = await db.select().from(flowChats).where(eq(flowChats.chatId, chatId));\n return row;\n }\n\n return {\n async pinChat(input) {\n await ready;\n const clean = withoutNul(input);\n await db\n .insert(flowChats)\n .values({\n chatId: clean.chatId,\n ownerId: clean.ownerId,\n flowId: clean.flowId,\n versionId: clean.versionId,\n stateId: clean.stateId,\n pending: [],\n timeZone: clean.timeZone,\n ...(clean.placement && { placement: clean.placement }),\n updatedAt: new Date(clean.at),\n })\n .onConflictDoNothing();\n return toRecord((await getChat(clean.chatId))!);\n },\n async getChat(chatId) {\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n await ready;\n const clean = withoutNul(patch);\n const [row] = await db\n .update(flowChats)\n .set({ versionId: clean.versionId, stateId: clean.stateId, timeZone: clean.timeZone, placement: clean.placement, updatedAt: new Date(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n await ready;\n await db.delete(flowChats).where(eq(flowChats.chatId, chatId));\n },\n\n async addPending(chatId, change) {\n await ready;\n await db.update(flowChats).set({ pending: pendingWith(withoutNul(change)) }).where(eq(flowChats.chatId, chatId));\n },\n async takePending(chatId) {\n await ready;\n // Старое значение — из строки, захваченной `for update`: вторая копия ждёт её и получает уже пустой список\n // (кроме переходов, применённых в срок: они остаются).\n const result = await db.execute<{ pending: PendingFlowChange[] }>(sql`\n with taken as (select chat_id, pending from ${flowChats} where chat_id = ${chatId} for update)\n update ${flowChats} set pending = (\n select coalesce(jsonb_agg(item order by place), '[]'::jsonb)\n from jsonb_array_elements(taken.pending) with ordinality as list(item, place)\n where item->>'kind' = 'transition' and item->'appliedAt' is not null\n ) from taken\n where ${flowChats.chatId} = taken.chat_id\n returning taken.pending`);\n return result.rows[0]?.pending ?? [];\n },\n\n async setDue(chatId, due, options) {\n await ready;\n await db\n .update(flowChats)\n .set({\n dueAt: due ? new Date(due.at) : null,\n dueExitId: due ? withoutNul(due.exitId) : null,\n dueBranchHead: due ? withoutNul(due.branchHead) : null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined));\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n await ready;\n const nowDate = new Date(now);\n // Строки, которые сейчас берёт другая копия, пропускаются; условие повторено снаружи — его база перепроверит\n // на новой версии строки, если та успела измениться.\n // Выборка — отдельным шагом `with`: подзапрос с пределом внутри `in (…)` база может перечитать и взять больше.\n const due = db.$with(\"due\").as(\n db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowDate))\n .orderBy(flowChats.dueAt, inCodeOrder(flowChats.chatId))\n .limit(limit)\n .for(\"update\", { skipLocked: true }),\n );\n const rows = await db\n .with(due)\n .update(flowChats)\n .set({ leaseOwner: withoutNul(owner), leaseUntil: new Date(nowDate.getTime() + leaseMs) })\n .where(and(inArray(flowChats.chatId, db.select({ chatId: due.chatId }).from(due)), dueAndFree(nowDate)))\n .returning();\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 await ready;\n await db\n .update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)));\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n await ready;\n const renewed = await db\n .update(flowChats)\n .set({ leaseUntil: new Date(new Date(now).getTime() + leaseMs) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, withoutNul(owner))))\n .returning({ chatId: flowChats.chatId });\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n await ready;\n const clean = withoutNul(input);\n const inserted = await db\n .insert(flowVersions)\n .values({ flowId: clean.flowId, versionId: clean.versionId, hash: clean.hash, flow: clean.flow, createdAt: new Date(clean.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash });\n if (inserted.length > 0) return \"new\";\n const [saved] = await db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, clean.flowId), eq(flowVersions.versionId, clean.versionId)));\n return saved?.hash === clean.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n await ready;\n const [row] = await db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)));\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n await ready;\n const rows = await db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)::int` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId);\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats(input) {\n await ready;\n const { flowId, exceptVersionId, chatIds } = withoutNul(input);\n if (chatIds?.length === 0) return [];\n const rows = await 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(inCodeOrder(flowChats.chatId));\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n await ready;\n const clean = withoutNul(input);\n const change = migratePending(clean);\n if (clean.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, clean.flowId),\n ne(flowChats.versionId, clean.toVersionId),\n clean.chatIds === undefined ? undefined : inArray(flowChats.chatId, clean.chatIds),\n );\n const batch = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(notYetMoved)\n .orderBy(inCodeOrder(flowChats.chatId))\n .limit(clean.limit)\n .for(\"update\", { skipLocked: true });\n const moved = await db\n .update(flowChats)\n .set({ versionId: clean.toVersionId, stateId: mappedStateSql(clean.map), pending: pendingWith(change), updatedAt: new Date(clean.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId });\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n await ready;\n // Значение читаем текстом и разбираем сами: иначе строка «007» из jsonb вернулась бы числом 7.\n const rows = await db\n .select({\n fieldId: flowClientCards.fieldId,\n value: sql<string>`${flowClientCards.value}::text`,\n updatedAt: flowClientCards.updatedAt,\n chatId: flowClientCards.chatId,\n flowId: flowClientCards.flowId,\n })\n .from(flowClientCards)\n .where(eq(flowClientCards.ownerId, ownerId));\n return rows\n .map((row) => ({ ...row, value: JSON.parse(row.value) as unknown, updatedAt: toIso(row.updatedAt) }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n await ready;\n const clean = withoutNul(input);\n const entry = { value: jsonb(clean.value), updatedAt: new Date(clean.at), chatId: clean.chatId, flowId: clean.flowId };\n await db\n .insert(flowClientCards)\n .values({ ownerId: clean.ownerId, fieldId: clean.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry });\n },\n async forgetCard(ownerId, fieldId) {\n await ready;\n await db\n .delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)));\n },\n\n async endChat(chatId, { reason, at }) {\n await ready;\n await db\n .update(flowChats)\n .set({ endedAt: new Date(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)));\n const row = await getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : new Date(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n await ready;\n await db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId));\n },\n async setDayClosed(chatId, day) {\n await ready;\n await db.update(flowChats).set({ dayClosedOn: day === null ? null : withoutNul(day) }).where(eq(flowChats.chatId, chatId));\n },\n async setDelivery(chatId, delivery) {\n await ready;\n const clean = delivery === null ? null : withoutNul({ ...delivery, retryAt: toIso(new Date(delivery.retryAt)) });\n await db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId));\n },\n\n async readTimeZone(ownerId) {\n await ready;\n const [row] = await db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId));\n return row?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n await ready;\n const clean = withoutNul({ ownerId, timeZone });\n await db\n .insert(flowPeople)\n .values({ ...clean, updatedAt: new Date(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone: clean.timeZone, updatedAt: new Date(at) } });\n },\n\n async forgetSideTasks(ownerId) {\n await ready;\n await db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId));\n },\n async readSideTasks(ownerId, chatId) {\n await ready;\n const rows = await db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))));\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async noteSideTask(input) {\n await ready;\n const clean = withoutNul(input);\n const asked = clean.status === \"asked\";\n const doneAt = asked ? null : new Date(clean.at);\n const [row] = await db\n .insert(flowSideTasks)\n .values({ ownerId: clean.ownerId, taskId: clean.taskId, chatId: clean.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}, excluded.done_at)` },\n })\n .returning();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n await ready;\n const [row] = await db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)));\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n await ready;\n const [row] = await db\n .insert(flowFirstMessages)\n .values({ ownerId: withoutNul(ownerId), day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count });\n return row!.count;\n },\n\n async close() {\n // Дожидаемся миграции: пул приложения можно закрывать сразу после.\n await ready.catch(() => {});\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 { boolean, index, integer, jsonb, pgSchema, primaryKey, text, timestamp } from \"drizzle-orm/pg-core\";\n\n/** Та же схема базы, что у хранилища чатов: у приложения одна база и одна схема `agent_chat`. */\nconst agentChatSchema = pgSchema(\"agent_chat\");\n\nconst moment = (name: string) => timestamp(name, { mode: \"date\", precision: 3, withTimezone: true });\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = agentChatSchema.table(\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: jsonb(\"pending\").$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: moment(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: moment(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: moment(\"updated_at\").notNull(),\n endedAt: moment(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: moment(\"paused_until\"),\n windowClosed: boolean(\"window_closed\").notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: jsonb(\"delivery\").$type<FlowPendingDelivery>(),\n placement: jsonb(\"placement\").$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 = agentChatSchema.table(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: jsonb(\"flow\").$type<AgentFlow>().notNull(),\n createdAt: moment(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = agentChatSchema.table(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: jsonb(\"value\").notNull(),\n updatedAt: moment(\"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 = agentChatSchema.table(\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 = agentChatSchema.table(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: moment(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = agentChatSchema.table(\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: moment(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":["jsonb"]}
package/dist/react.js CHANGED
@@ -108,7 +108,8 @@ var russianFlowEditorLabels = {
108
108
  saveFailedHint: "\u0421\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0435 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E. \u0427\u0442\u043E \u043D\u0435 \u0442\u0430\u043A \u2014 \u0432 \xAB\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0433\u0440\u0430\u0444\xBB \u0438 \u043D\u0430 \u043D\u043E\u0434\u0430\u0445.",
109
109
  readOnly: "\u0422\u043E\u043B\u044C\u043A\u043E \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440",
110
110
  readOnlyHint: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u0441\u0435\u0439\u0447\u0430\u0441 \u043F\u0440\u0430\u0432\u0438\u0442 \u0430\u0433\u0435\u043D\u0442. \u0421\u043C\u043E\u0442\u0440\u0435\u0442\u044C \u0438 \u043F\u0440\u043E\u0433\u043E\u043D\u044F\u0442\u044C \u043C\u043E\u0436\u043D\u043E, \u043F\u0440\u0430\u0432\u0438\u0442\u044C \u043D\u0435\u043B\u044C\u0437\u044F.",
111
- migrate: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B"
111
+ migrate: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B",
112
+ more: "\u0415\u0449\u0451 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044F"
112
113
  },
113
114
  board: {
114
115
  anyState: "\u0418\u0437 \u043B\u044E\u0431\u043E\u0433\u043E \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F",
@@ -354,7 +355,8 @@ var englishFlowEditorLabels = {
354
355
  saveFailedHint: "Saving was rejected. See what is wrong in \u201CCheck flow\u201D and on the nodes.",
355
356
  readOnly: "View only",
356
357
  readOnlyHint: "An agent is editing this flow right now. You can look and run it, but not edit.",
357
- migrate: "Move chats"
358
+ migrate: "Move chats",
359
+ more: "More actions"
358
360
  },
359
361
  board: {
360
362
  anyState: "From any state",
@@ -567,7 +569,7 @@ var englishFlowEditorLabels = {
567
569
  // src/labels/migrate.ts
568
570
  var russianFlowMigrateLabels = {
569
571
  title: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442\u044B \u043D\u0430 \u043D\u043E\u0432\u0443\u044E \u0432\u0435\u0440\u0441\u0438\u044E",
570
- hint: "\u0427\u0430\u0442\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442 \u0441 \u0442\u043E\u0433\u043E \u0436\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F. \u0414\u043B\u044F \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u044B\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u0443\u0434\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438.",
572
+ hint: "\u0427\u0430\u0442\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0430\u0442 \u0441 \u0442\u043E\u0433\u043E \u0436\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F. \u0417\u0430\u043A\u043E\u043D\u0447\u0438\u0432\u0448\u0438\u0435\u0441\u044F \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u044B \u0438 \u0442\u0435\u0441\u0442\u043E\u0432\u044B\u0435 \u0447\u0430\u0442\u044B \u043E\u0441\u0442\u0430\u044E\u0442\u0441\u044F \u043D\u0430 \u0441\u0432\u043E\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438. \u0414\u043B\u044F \u0443\u0434\u0430\u043B\u0451\u043D\u043D\u044B\u0445 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0439 \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435, \u043A\u0443\u0434\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438.",
571
573
  version: "\u0412\u0435\u0440\u0441\u0438\u044F",
572
574
  state: "\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
573
575
  chats: "\u0427\u0430\u0442\u043E\u0432",
@@ -576,13 +578,13 @@ var russianFlowMigrateLabels = {
576
578
  chooseTarget: "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435",
577
579
  migrate: "\u041F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438",
578
580
  moved: (count) => `\u041F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u043E \u0447\u0430\u0442\u043E\u0432: ${count}`,
579
- nothingToMigrate: "\u0412\u0441\u0435 \u0447\u0430\u0442\u044B \u0443\u0436\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.",
581
+ nothingToMigrate: "\u041F\u0435\u0440\u0435\u0432\u043E\u0434\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E: \u0438\u0434\u0443\u0449\u0438\u0435 \u0447\u0430\u0442\u044B \u0443\u0436\u0435 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438.",
580
582
  close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
581
583
  failed: "\u041D\u0435 \u043F\u043E\u043B\u0443\u0447\u0438\u043B\u043E\u0441\u044C \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437"
582
584
  };
583
585
  var englishFlowMigrateLabels = {
584
586
  title: "Move chats to the new version",
585
- hint: "Chats continue from the same state. For removed states choose where to move them.",
587
+ hint: "Chats continue from the same state. Finished conversations and test chats stay on their version. For removed states choose where to move them.",
586
588
  version: "Version",
587
589
  state: "State",
588
590
  chats: "Chats",
@@ -591,7 +593,7 @@ var englishFlowMigrateLabels = {
591
593
  chooseTarget: "Choose a state",
592
594
  migrate: "Move",
593
595
  moved: (count) => `Chats moved: ${count}`,
594
- nothingToMigrate: "All chats are already on this version.",
596
+ nothingToMigrate: "Nothing to move: ongoing chats are already on this version.",
595
597
  close: "Close",
596
598
  failed: "Could not move the chats. Try again"
597
599
  };
@@ -1936,7 +1938,7 @@ function useMigrateController(flow, migration, show) {
1936
1938
  try {
1937
1939
  const result = await migration.migrate(map);
1938
1940
  setMoved(result.moved);
1939
- setChats([]);
1941
+ setChats(await migration.chatsByState().catch(() => []));
1940
1942
  } catch {
1941
1943
  setFailed(true);
1942
1944
  } finally {