@ykurilov/agent-chat-flows 0.66.0 → 0.68.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/agent-flow.schema.json +12 -0
- package/dist/{chunk-7VR4YNTJ.js → chunk-2LAVG3YR.js} +42 -9
- package/dist/chunk-2LAVG3YR.js.map +1 -0
- package/dist/{chunk-KRZMJJKG.js → chunk-DQQ2GCQ2.js} +22 -11
- package/dist/chunk-DQQ2GCQ2.js.map +1 -0
- package/dist/{chunk-L7XNC3RB.js → chunk-PLY57QT5.js} +2 -2
- package/dist/format.js +2 -2
- package/dist/index.js +63 -7
- package/dist/index.js.map +1 -1
- package/dist/postgres.js +7 -0
- package/dist/postgres.js.map +1 -1
- package/dist/react.js +51 -9
- package/dist/react.js.map +1 -1
- package/dist/sqlite.js +6 -0
- package/dist/sqlite.js.map +1 -1
- package/dist/types/engine/flow-run.d.ts +4 -2
- package/dist/types/engine/nudges.d.ts +1 -1
- package/dist/types/engine/run-updates.d.ts +5 -0
- package/dist/types/format/flow-format.d.ts +10 -0
- package/dist/types/format/flow-schema.d.ts +6 -0
- package/dist/types/labels/editor.d.ts +5 -1
- package/dist/types/react/editor/index.d.ts +1 -1
- package/dist/types/react/editor/schedule-model.d.ts +9 -1
- package/dist/types/react/flow-editor-skin.d.ts +3 -0
- package/dist/types/runtime/first-message-rules.d.ts +2 -0
- package/dist/types/runtime/flow-events.d.ts +6 -1
- package/dist/types/runtime/flow-types.d.ts +52 -0
- package/dist/types/store/flow-store.d.ts +10 -0
- package/dist/types/store/postgres-schema.d.ts +17 -0
- package/dist/types/store/sqlite-schema.d.ts +17 -0
- package/drizzle/postgres/0003_chat_reopen.sql +1 -0
- package/drizzle/postgres/meta/0003_snapshot.json +451 -0
- package/drizzle/postgres/meta/_journal.json +7 -0
- package/drizzle/sqlite/0003_chat_reopen.sql +1 -0
- package/drizzle/sqlite/meta/0003_snapshot.json +441 -0
- package/drizzle/sqlite/meta/_journal.json +7 -0
- package/package.json +4 -4
- package/dist/chunk-7VR4YNTJ.js.map +0 -1
- package/dist/chunk-KRZMJJKG.js.map +0 -1
- /package/dist/{chunk-L7XNC3RB.js.map → chunk-PLY57QT5.js.map} +0 -0
package/dist/postgres.js
CHANGED
|
@@ -40,6 +40,7 @@ var flowChats = agentChatSchema.table(
|
|
|
40
40
|
updatedAt: moment("updated_at").notNull(),
|
|
41
41
|
endedAt: moment("ended_at"),
|
|
42
42
|
endReason: text("end_reason").$type(),
|
|
43
|
+
reopenedAt: moment("reopened_at"),
|
|
43
44
|
pausedUntil: moment("paused_until"),
|
|
44
45
|
windowClosed: boolean("window_closed").notNull().default(false),
|
|
45
46
|
dayClosedOn: text("day_closed_on"),
|
|
@@ -118,6 +119,7 @@ function toRecord(row) {
|
|
|
118
119
|
updatedAt: toIso(row.updatedAt),
|
|
119
120
|
endedAt: row.endedAt === null ? null : toIso(row.endedAt),
|
|
120
121
|
endReason: row.endReason,
|
|
122
|
+
reopenedAt: row.reopenedAt === null ? null : toIso(row.reopenedAt),
|
|
121
123
|
pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),
|
|
122
124
|
windowClosed: row.windowClosed,
|
|
123
125
|
dayClosedOn: row.dayClosedOn,
|
|
@@ -326,6 +328,11 @@ function createPostgresFlowStore(pool, migrationsFolder) {
|
|
|
326
328
|
const row = await getChat(chatId);
|
|
327
329
|
return row ? toRecord(row) : null;
|
|
328
330
|
},
|
|
331
|
+
async reopenChat(chatId, { endedAt, at }) {
|
|
332
|
+
await ready;
|
|
333
|
+
const [row] = await db.update(flowChats).set({ endedAt: null, endReason: null, reopenedAt: new Date(at) }).where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, new Date(endedAt)))).returning();
|
|
334
|
+
return row ? toRecord(row) : null;
|
|
335
|
+
},
|
|
329
336
|
async setPause(chatId, until) {
|
|
330
337
|
await ready;
|
|
331
338
|
const [row] = await db.update(flowChats).set({ pausedUntil: until === null ? null : new Date(until) }).where(eq(flowChats.chatId, chatId)).returning();
|
package/dist/postgres.js.map
CHANGED
|
@@ -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 { migratePostgresSchema } from \"@ykurilov/agent-chat-core\";\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 * Приводит таблицы сценариев к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: FLOWS_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE }),\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"]}
|
|
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 { migratePostgresSchema } from \"@ykurilov/agent-chat-core\";\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 reopenedAt: row.reopenedAt === null ? null : toIso(row.reopenedAt),\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 * Приводит таблицы сценариев к последней миграции. Схема `agent_chat` создаётся под общей блокировкой всех пакетов, миграции — под своей:\n * копии пакета ждут друг друга, а разные пакеты стартуют разом на пустой базе без гонки за схему.\n */\nexport async function migrateFlowsToLatest(pool: Pool, migrationsFolder: string): Promise<void> {\n await migratePostgresSchema({\n connect: () => pool.connect(),\n lockKey: FLOWS_MIGRATION_LOCK_KEY,\n migrate: (client) => migrate(drizzle(client), { migrationsFolder, migrationsSchema: \"agent_chat\", migrationsTable: FLOWS_MIGRATIONS_TABLE }),\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 reopenChat(chatId, { endedAt, at }) {\n await ready;\n const [row] = await db\n .update(flowChats)\n .set({ endedAt: null, endReason: null, reopenedAt: new Date(at) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, new Date(endedAt))))\n .returning();\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 reopenedAt: moment(\"reopened_at\"),\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
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
FLOW_DEADLINE_NOTICE,
|
|
4
4
|
FLOW_TIMER_NOTICE,
|
|
5
5
|
fieldValueText
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DQQ2GCQ2.js";
|
|
7
7
|
import {
|
|
8
8
|
ANY_STATE_LAYOUT_KEY,
|
|
9
9
|
DEFAULT_LATE_START_HOURS,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
startRun,
|
|
32
32
|
stateById,
|
|
33
33
|
timerDueAt
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-2LAVG3YR.js";
|
|
35
35
|
|
|
36
36
|
// src/react/flow-labels.ts
|
|
37
37
|
import { createContext, createElement, useContext } from "react";
|
|
@@ -130,6 +130,7 @@ var russianFlowEditorLabels = {
|
|
|
130
130
|
nudges: "\u043A\u0430\u0441\u0430\u043D\u0438\u044F",
|
|
131
131
|
noNudges: "\u0431\u0435\u0437 \u043A\u0430\u0441\u0430\u043D\u0438\u0439",
|
|
132
132
|
nudgesDays: (days) => days === 1 ? "\u0432 \u0434\u0435\u043D\u044C \u0432\u0445\u043E\u0434\u0430" : `\u043F\u0435\u0440\u0432\u044B\u0435 ${days} \u0434\u043D.`,
|
|
133
|
+
nudgesUntilProgramDay: (day) => `\u0434\u043E \u0434\u043D\u044F ${day} \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B`,
|
|
133
134
|
space: {
|
|
134
135
|
legend: "\u041B\u0435\u0433\u0435\u043D\u0434\u0430",
|
|
135
136
|
showMinimap: "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u043C\u0438\u043D\u0438-\u043A\u0430\u0440\u0442\u0443",
|
|
@@ -178,6 +179,8 @@ var russianFlowEditorLabels = {
|
|
|
178
179
|
firstMessages: "\u0421\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F \u043F\u0435\u0440\u0432\u044B\u043C",
|
|
179
180
|
maxPerDay: "\u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0432 \u0434\u0435\u043D\u044C",
|
|
180
181
|
maxPerDayHint: "\u041F\u0443\u0441\u0442\u043E \u2014 \u043A\u0430\u043A \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F.",
|
|
182
|
+
maxPerDayByDay: "\u041D\u0435 \u0431\u043E\u043B\u044C\u0448\u0435 \u0432 \u0434\u0435\u043D\u044C \u043F\u043E \u0434\u043D\u044F\u043C \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B",
|
|
183
|
+
maxPerDayByDayHint: "\u0427\u0435\u0440\u0435\u0437 \u0437\u0430\u043F\u044F\u0442\u0443\u044E: \xAB3, 2\xBB \u2014 \u0432 \u0434\u0435\u043D\u044C 1 \u0442\u0440\u0438, \u0441\u043E \u0434\u043D\u044F 2 \u043F\u043E \u0434\u0432\u0430. \u0413\u043B\u0430\u0432\u043D\u0435\u0435 \u043F\u043E\u043B\u044F \u0432\u044B\u0448\u0435. \u041E\u0442\u0432\u0435\u0442 \u0447\u0435\u043B\u043E\u0432\u0435\u043A\u0430 \u0441\u0447\u0451\u0442 \u043D\u0435 \u043E\u0431\u043D\u0443\u043B\u044F\u0435\u0442.",
|
|
181
184
|
morning: "\u0423\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0435",
|
|
182
185
|
skipMorning: "\u041D\u0435 \u043F\u0438\u0441\u0430\u0442\u044C \u0443\u0442\u0440\u043E\u043C, \u0435\u0441\u043B\u0438 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043F\u0438\u0441\u0430\u043B \u0437\u0430, \u043C\u0438\u043D",
|
|
183
186
|
nudges: "\u041A\u0430\u0441\u0430\u043D\u0438\u044F \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E",
|
|
@@ -242,8 +245,9 @@ var russianFlowEditorLabels = {
|
|
|
242
245
|
nudgesModes: { inherit: "\u041A\u0430\u043A \u0443 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F", own: "\u0421\u0432\u043E\u0438", off: "\u0412\u044B\u043A\u043B" },
|
|
243
246
|
nudgesHint: "\u0410\u0433\u0435\u043D\u0442 \u043F\u0438\u0448\u0435\u0442 \u0437\u0434\u0435\u0441\u044C \u0441\u0430\u043C, \u043A\u043E\u0433\u0434\u0430 \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u043C\u043E\u043B\u0447\u0438\u0442.",
|
|
244
247
|
nudgesDays: "\u0421\u0432\u043E\u0438 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044E\u0442",
|
|
245
|
-
nudgesDaysModes: { always: "\u0432\u0441\u0451 \u0432\u0440\u0435\u043C\u044F \u0432 \u043D\u043E\u0434\u0435", days: "\u0434\u043D., \u0441\u0447\u0438\u0442\u0430\u044F \u0434\u0435\u043D\u044C \u0432\u0445\u043E\u0434\u0430" },
|
|
246
|
-
nudgesDaysHint: "\u041F\u043E\u0442\u043E\u043C \u2014 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: \u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0438 \u043E\u0431\u0449\u0438\u0439 \u0440\u044F\u0434 \u0441 \u043D\u0430\u0447\u0430\u043B\u0430."
|
|
248
|
+
nudgesDaysModes: { always: "\u0432\u0441\u0451 \u0432\u0440\u0435\u043C\u044F \u0432 \u043D\u043E\u0434\u0435", days: "\u0434\u043D., \u0441\u0447\u0438\u0442\u0430\u044F \u0434\u0435\u043D\u044C \u0432\u0445\u043E\u0434\u0430", untilProgramDay: "\u0434\u043E \u0434\u043D\u044F \u043F\u0440\u043E\u0433\u0440\u0430\u043C\u043C\u044B \u0432\u043A\u043B\u044E\u0447\u0438\u0442\u0435\u043B\u044C\u043D\u043E" },
|
|
249
|
+
nudgesDaysHint: "\u041F\u043E\u0442\u043E\u043C \u2014 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: \u0443\u0442\u0440\u0435\u043D\u043D\u0435\u0435 \u0438 \u043E\u0431\u0449\u0438\u0439 \u0440\u044F\u0434 \u0441 \u043D\u0430\u0447\u0430\u043B\u0430.",
|
|
250
|
+
nudgesUntilProgramDayHint: "\u041F\u0440\u0438\u0448\u0451\u043B \u0432 \u043D\u043E\u0434\u0443 \u043F\u043E\u0437\u0436\u0435 \u2014 \u0441\u0432\u043E\u0438\u0445 \u043A\u0430\u0441\u0430\u043D\u0438\u0439 \u043D\u0435\u0442, \u0441\u0440\u0430\u0437\u0443 \u043A\u0430\u0441\u0430\u043D\u0438\u044F \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F."
|
|
247
251
|
},
|
|
248
252
|
exitCard: {
|
|
249
253
|
when: "\u041A\u043E\u0433\u0434\u0430",
|
|
@@ -377,6 +381,7 @@ var englishFlowEditorLabels = {
|
|
|
377
381
|
nudges: "nudges",
|
|
378
382
|
noNudges: "no nudges",
|
|
379
383
|
nudgesDays: (days) => days === 1 ? "on the entry day" : `for the first ${days} days`,
|
|
384
|
+
nudgesUntilProgramDay: (day) => `until program day ${day}`,
|
|
380
385
|
space: {
|
|
381
386
|
legend: "Legend",
|
|
382
387
|
showMinimap: "Show the minimap",
|
|
@@ -425,6 +430,8 @@ var englishFlowEditorLabels = {
|
|
|
425
430
|
firstMessages: "Writing first",
|
|
426
431
|
maxPerDay: "At most per day",
|
|
427
432
|
maxPerDayHint: "Empty \u2014 as in the app settings.",
|
|
433
|
+
maxPerDayByDay: "At most per day, by program day",
|
|
434
|
+
maxPerDayByDayHint: "Comma-separated: \u201C3, 2\u201D \u2014 three on day 1, two from day 2 on. Overrides the field above. A reply doesn't reset the count.",
|
|
428
435
|
morning: "Morning message",
|
|
429
436
|
skipMorning: "Skip the morning if the person wrote within, min",
|
|
430
437
|
nudges: "Default nudges",
|
|
@@ -489,8 +496,9 @@ var englishFlowEditorLabels = {
|
|
|
489
496
|
nudgesModes: { inherit: "As in the flow", own: "Own", off: "Off" },
|
|
490
497
|
nudgesHint: "The agent writes here by itself while the person is silent.",
|
|
491
498
|
nudgesDays: "Own nudges apply",
|
|
492
|
-
nudgesDaysModes: { always: "the whole time in the state", days: "days, counting the entry day" },
|
|
493
|
-
nudgesDaysHint: "Then the flow's nudges: the morning message and the common series from the start."
|
|
499
|
+
nudgesDaysModes: { always: "the whole time in the state", days: "days, counting the entry day", untilProgramDay: "until program day, inclusive" },
|
|
500
|
+
nudgesDaysHint: "Then the flow's nudges: the morning message and the common series from the start.",
|
|
501
|
+
nudgesUntilProgramDayHint: "Reached the state later \u2014 no own nudges, the flow's nudges right away."
|
|
494
502
|
},
|
|
495
503
|
exitCard: {
|
|
496
504
|
when: "When",
|
|
@@ -1241,8 +1249,15 @@ function updateStateNudges(flow, stateId, nudges) {
|
|
|
1241
1249
|
...flow,
|
|
1242
1250
|
states: flow.states.map((state) => {
|
|
1243
1251
|
if (state.id !== stateId) return state;
|
|
1244
|
-
const
|
|
1245
|
-
return {
|
|
1252
|
+
const own = state.nudges && state.nudges !== "off" ? state.nudges : void 0;
|
|
1253
|
+
return {
|
|
1254
|
+
...state,
|
|
1255
|
+
nudges: {
|
|
1256
|
+
...cleanNudges(nudges),
|
|
1257
|
+
...own?.days !== void 0 && { days: own.days },
|
|
1258
|
+
...own?.untilProgramDay !== void 0 && { untilProgramDay: own.untilProgramDay }
|
|
1259
|
+
}
|
|
1260
|
+
};
|
|
1246
1261
|
})
|
|
1247
1262
|
};
|
|
1248
1263
|
}
|
|
@@ -1259,6 +1274,28 @@ function setStateNudgesDays(flow, stateId, days) {
|
|
|
1259
1274
|
function stateNudgesDays(state) {
|
|
1260
1275
|
return state.nudges && state.nudges !== "off" ? state.nudges.days ?? null : null;
|
|
1261
1276
|
}
|
|
1277
|
+
function setStateNudgesUntilProgramDay(flow, stateId, day) {
|
|
1278
|
+
return {
|
|
1279
|
+
...flow,
|
|
1280
|
+
states: flow.states.map((state) => {
|
|
1281
|
+
if (state.id !== stateId || !state.nudges || state.nudges === "off") return state;
|
|
1282
|
+
const { untilProgramDay: _until, ...rest } = state.nudges;
|
|
1283
|
+
return { ...state, nudges: day === null ? rest : { ...rest, untilProgramDay: Math.max(1, Math.round(day) || 1) } };
|
|
1284
|
+
})
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
function stateNudgesUntilProgramDay(state) {
|
|
1288
|
+
return state.nudges && state.nudges !== "off" ? state.nudges.untilProgramDay ?? null : null;
|
|
1289
|
+
}
|
|
1290
|
+
function capsByDayText(caps) {
|
|
1291
|
+
return caps?.join(", ") ?? "";
|
|
1292
|
+
}
|
|
1293
|
+
function parseCapsByDay(text) {
|
|
1294
|
+
const parts = text.split(/[,;\s]+/).filter(Boolean);
|
|
1295
|
+
if (parts.length === 0) return void 0;
|
|
1296
|
+
const caps = parts.map(Number);
|
|
1297
|
+
return caps.every((cap) => Number.isInteger(cap) && cap >= 0) ? caps : null;
|
|
1298
|
+
}
|
|
1262
1299
|
function updateScheduleNudges(flow, nudges) {
|
|
1263
1300
|
return updateSchedule(flow, { nudges: nudges === null ? void 0 : cleanNudges(nudges) });
|
|
1264
1301
|
}
|
|
@@ -1282,7 +1319,8 @@ function stateTimingText(flow, state, labels) {
|
|
|
1282
1319
|
const steps = state.nudges.afterSilence.map((step2) => durationText(step2, labels));
|
|
1283
1320
|
const daily = state.nudges.dailyAt ?? [];
|
|
1284
1321
|
const days = state.nudges.days === void 0 ? "" : ` ${labels.board.nudgesDays(state.nudges.days)}`;
|
|
1285
|
-
|
|
1322
|
+
const until = state.nudges.untilProgramDay === void 0 ? "" : ` ${labels.board.nudgesUntilProgramDay(state.nudges.untilProgramDay)}`;
|
|
1323
|
+
if (steps.length + daily.length > 0) parts.push(`${labels.board.nudges} ${[...steps, ...daily].join(" \xB7 ")}${days}${until}`);
|
|
1286
1324
|
}
|
|
1287
1325
|
return parts.join(" \xB7 ");
|
|
1288
1326
|
}
|
|
@@ -2353,6 +2391,8 @@ function StateTiming({ editor, skin, labels, state }) {
|
|
|
2353
2391
|
onNudgesModeChange: (next) => editor.edit((current) => setStateNudges(current, state.id, next)),
|
|
2354
2392
|
nudgesDays: stateNudgesDays(state),
|
|
2355
2393
|
onNudgesDaysChange: (days) => editor.edit((current) => setStateNudgesDays(current, state.id, days)),
|
|
2394
|
+
nudgesUntilProgramDay: stateNudgesUntilProgramDay(state),
|
|
2395
|
+
onNudgesUntilProgramDayChange: (day) => editor.edit((current) => setStateNudgesUntilProgramDay(current, state.id, day)),
|
|
2356
2396
|
nudges: own && /* @__PURE__ */ jsx2(
|
|
2357
2397
|
NudgesEditor,
|
|
2358
2398
|
{
|
|
@@ -2805,6 +2845,7 @@ export {
|
|
|
2805
2845
|
FlowEditor,
|
|
2806
2846
|
FlowLabelsProvider,
|
|
2807
2847
|
FlowTestChat,
|
|
2848
|
+
capsByDayText,
|
|
2808
2849
|
chooseExit,
|
|
2809
2850
|
chooseNothing,
|
|
2810
2851
|
conditionText,
|
|
@@ -2815,6 +2856,7 @@ export {
|
|
|
2815
2856
|
formatDayLine,
|
|
2816
2857
|
formatDuration,
|
|
2817
2858
|
nextQuestionField,
|
|
2859
|
+
parseCapsByDay,
|
|
2818
2860
|
russianFlowLabels,
|
|
2819
2861
|
russianFlowNoticeLabels,
|
|
2820
2862
|
sameFlow,
|