@ykurilov/agent-chat-flows 0.67.0 → 0.69.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 +19 -0
- package/dist/{chunk-C7PMUVRN.js → chunk-2LAVG3YR.js} +20 -5
- package/dist/chunk-2LAVG3YR.js.map +1 -0
- package/dist/{chunk-H2TPYIWU.js → chunk-DQQ2GCQ2.js} +13 -6
- package/dist/chunk-DQQ2GCQ2.js.map +1 -0
- package/dist/{chunk-2ZHBD7MF.js → chunk-PLY57QT5.js} +2 -2
- package/dist/format.js +2 -2
- package/dist/index.js +62 -6
- package/dist/index.js.map +1 -1
- package/dist/postgres.js +7 -0
- package/dist/postgres.js.map +1 -1
- package/dist/react.js +2 -2
- 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/run-updates.d.ts +5 -0
- package/dist/types/runtime/flow-events.d.ts +6 -1
- package/dist/types/runtime/flow-types.d.ts +50 -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-C7PMUVRN.js.map +0 -1
- package/dist/chunk-H2TPYIWU.js.map +0 -1
- /package/dist/{chunk-2ZHBD7MF.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";
|
package/dist/sqlite.js
CHANGED
|
@@ -38,6 +38,7 @@ var flowChats = sqliteTable(
|
|
|
38
38
|
updatedAt: integer("updated_at").notNull(),
|
|
39
39
|
endedAt: integer("ended_at"),
|
|
40
40
|
endReason: text("end_reason").$type(),
|
|
41
|
+
reopenedAt: integer("reopened_at"),
|
|
41
42
|
pausedUntil: integer("paused_until"),
|
|
42
43
|
windowClosed: integer("window_closed", { mode: "boolean" }).notNull().default(false),
|
|
43
44
|
dayClosedOn: text("day_closed_on"),
|
|
@@ -117,6 +118,7 @@ function toRecord(row) {
|
|
|
117
118
|
updatedAt: toIso(row.updatedAt),
|
|
118
119
|
endedAt: row.endedAt === null ? null : toIso(row.endedAt),
|
|
119
120
|
endReason: row.endReason,
|
|
121
|
+
reopenedAt: row.reopenedAt === null ? null : toIso(row.reopenedAt),
|
|
120
122
|
pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),
|
|
121
123
|
windowClosed: row.windowClosed,
|
|
122
124
|
dayClosedOn: row.dayClosedOn,
|
|
@@ -277,6 +279,10 @@ function createSqliteFlowStore(filePath, migrationsFolder) {
|
|
|
277
279
|
const row = getChat(chatId);
|
|
278
280
|
return row ? toRecord(row) : null;
|
|
279
281
|
},
|
|
282
|
+
async reopenChat(chatId, { endedAt, at }) {
|
|
283
|
+
const row = db.update(flowChats).set({ endedAt: null, endReason: null, reopenedAt: toMs(at) }).where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, toMs(endedAt)))).returning().get();
|
|
284
|
+
return row ? toRecord(row) : null;
|
|
285
|
+
},
|
|
280
286
|
async setPause(chatId, until) {
|
|
281
287
|
const row = db.update(flowChats).set({ pausedUntil: until === null ? null : toMs(until) }).where(eq(flowChats.chatId, chatId)).returning().get();
|
|
282
288
|
return row ? toRecord(row) : null;
|
package/dist/sqlite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sqlite.ts","../src/store/sqlite.ts","../src/store/sqlite-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { createSqliteFlowStore, type SqliteFlowStore } from \"./store/sqlite\";\n\nexport type { SqliteFlowStore } from \"./store/sqlite\";\nexport { FLOWS_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/sqlite-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/sqlite`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/sqlite\", import.meta.url));\n\n/**\n * Сценарии в файле SQLite; файл может быть общим с хранилищем чатов (`createSqliteChatStorage`).\n * Таблицы приводятся к последней миграции при открытии.\n */\nexport function sqliteFlowStore(filePath: string): SqliteFlowStore {\n return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);\n}\n","import Database from \"better-sqlite3\";\nimport { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { migrate } from \"drizzle-orm/better-sqlite3/migrator\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { appliedInTime, byVersionAndState, compareText, migratePending, pendingAdded } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./sqlite-schema\";\n\n/** Хранилище в файле SQLite: `close` обязателен — файл закрывает приложение при остановке. */\nexport type SqliteFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toMs = (iso: string) => Date.parse(iso);\nconst toIso = (ms: number) => new Date(ms).toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** Дописать изменение в конец `pending`, если такого ключа там ещё нет. Одно выражение — одна запись базы. */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})\n then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: number) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/**\n * Сценарии в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим с хранилищем\n * чатов. Несколько копий сервера на одном файле делят таймеры арендой: каждое изменение — одно условное `update`\n * или короткая транзакция с захватом записи (`immediate`).\n */\nexport function createSqliteFlowStore(filePath: string, migrationsFolder: string): SqliteFlowStore {\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"busy_timeout = 5000\");\n const db = drizzle(sqlite);\n migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });\n\n const getChat = (chatId: string) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n\n return {\n async pinChat(input) {\n db.insert(flowChats)\n .values({\n chatId: input.chatId,\n ownerId: input.ownerId,\n flowId: input.flowId,\n versionId: input.versionId,\n stateId: input.stateId,\n pending: [],\n timeZone: input.timeZone,\n ...(input.placement && { placement: input.placement }),\n updatedAt: toMs(input.at),\n })\n .onConflictDoNothing()\n .run();\n return toRecord(getChat(input.chatId)!);\n },\n async getChat(chatId) {\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n const row = db\n .update(flowChats)\n .set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async addPending(chatId, change) {\n // Прочитать и дописать под захватом записи: две копии с одним ключом не дают двух записей.\n const add = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n const next = row && pendingAdded(row.pending, change);\n if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();\n });\n add.immediate();\n },\n async takePending(chatId) {\n // Прочитать и очистить под захватом записи: вторая копия ждёт и видит уже пустой список (кроме применённых в срок).\n const take = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n if (!row || row.pending.length === 0) return [];\n db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();\n return row.pending;\n });\n return take.immediate();\n },\n\n async setDue(chatId, due, options) {\n db.update(flowChats)\n .set({\n dueAt: due ? toMs(due.at) : null,\n dueExitId: due?.exitId ?? null,\n dueBranchHead: due?.branchHead ?? null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined))\n .run();\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n const nowMs = toMs(now);\n // Выбор и аренда — одно `update`: из двух копий, пришедших одновременно, строку получит только первая.\n const due = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowMs))\n .orderBy(asc(flowChats.dueAt), asc(flowChats.chatId))\n .limit(limit);\n const rows = db\n .update(flowChats)\n .set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs })\n .where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs)))\n .returning()\n .all();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n db.update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .run();\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n const renewed = db\n .update(flowChats)\n .set({ leaseUntil: toMs(now) + leaseMs })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .returning({ chatId: flowChats.chatId })\n .all();\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n const inserted = db\n .insert(flowVersions)\n .values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash })\n .all();\n if (inserted.length > 0) return \"new\";\n const saved = db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId)))\n .get();\n return saved?.hash === input.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n const row = db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)))\n .get();\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n const rows = db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId)\n .all();\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats({ flowId, exceptVersionId, chatIds }) {\n if (chatIds?.length === 0) return [];\n const rows = db\n .select({\n chatId: flowChats.chatId,\n ownerId: flowChats.ownerId,\n flowId: flowChats.flowId,\n versionId: flowChats.versionId,\n stateId: flowChats.stateId,\n endedAt: flowChats.endedAt,\n endReason: flowChats.endReason,\n })\n .from(flowChats)\n .where(\n and(\n eq(flowChats.flowId, flowId),\n exceptVersionId === undefined ? undefined : ne(flowChats.versionId, exceptVersionId),\n chatIds === undefined ? undefined : inArray(flowChats.chatId, chatIds),\n ),\n )\n .orderBy(asc(flowChats.chatId))\n .all();\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n const change = migratePending(input);\n if (input.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, input.flowId),\n ne(flowChats.versionId, input.toVersionId),\n input.chatIds === undefined ? undefined : inArray(flowChats.chatId, input.chatIds),\n );\n const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);\n const moved = db\n .update(flowChats)\n .set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId })\n .all();\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();\n return rows\n .map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };\n db.insert(flowClientCards)\n .values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry })\n .run();\n },\n async forgetCard(ownerId, fieldId) {\n db.delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)))\n .run();\n },\n\n async endChat(chatId, { reason, at }) {\n db.update(flowChats)\n .set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)))\n .run();\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n const row = db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : toMs(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDayClosed(chatId, day) {\n db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDelivery(chatId, delivery) {\n const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };\n db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async readTimeZone(ownerId) {\n return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n db.insert(flowPeople)\n .values({ ownerId, timeZone, updatedAt: toMs(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } })\n .run();\n },\n\n async readSideTasks(ownerId, chatId) {\n const rows = db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))))\n .all();\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async forgetSideTasks(ownerId) {\n db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();\n },\n async noteSideTask(input) {\n const asked = input.status === \"asked\";\n const doneAt = asked ? null : toMs(input.at);\n const row = db\n .insert(flowSideTasks)\n .values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` },\n })\n .returning()\n .get();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n const row = db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)))\n .get();\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n const row = db\n .insert(flowFirstMessages)\n .values({ ownerId, day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count })\n .get();\n return row!.count;\n },\n\n async close() {\n if (sqlite.open) sqlite.close();\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { index, integer, primaryKey, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n// Времена — миллисекунды с 1970 года, JSON — текстом.\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = sqliteTable(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: text(\"pending\", { mode: \"json\" }).$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: integer(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: integer(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: integer(\"updated_at\").notNull(),\n endedAt: integer(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: integer(\"paused_until\"),\n windowClosed: integer(\"window_closed\", { mode: \"boolean\" }).notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: text(\"delivery\", { mode: \"json\" }).$type<FlowPendingDelivery>(),\n placement: text(\"placement\", { mode: \"json\" }).$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = sqliteTable(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: text(\"flow\", { mode: \"json\" }).$type<AgentFlow>().notNull(),\n createdAt: integer(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = sqliteTable(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: text(\"value\", { mode: \"json\" }).notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = sqliteTable(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = sqliteTable(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = sqliteTable(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: integer(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/sqlite.ts","../src/store/sqlite.ts","../src/store/sqlite-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { createSqliteFlowStore, type SqliteFlowStore } from \"./store/sqlite\";\n\nexport type { SqliteFlowStore } from \"./store/sqlite\";\nexport { FLOWS_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/sqlite-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/sqlite`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/sqlite\", import.meta.url));\n\n/**\n * Сценарии в файле SQLite; файл может быть общим с хранилищем чатов (`createSqliteChatStorage`).\n * Таблицы приводятся к последней миграции при открытии.\n */\nexport function sqliteFlowStore(filePath: string): SqliteFlowStore {\n return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);\n}\n","import Database from \"better-sqlite3\";\nimport { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { migrate } from \"drizzle-orm/better-sqlite3/migrator\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { appliedInTime, byVersionAndState, compareText, migratePending, pendingAdded } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./sqlite-schema\";\n\n/** Хранилище в файле SQLite: `close` обязателен — файл закрывает приложение при остановке. */\nexport type SqliteFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toMs = (iso: string) => Date.parse(iso);\nconst toIso = (ms: number) => new Date(ms).toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n 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/** Дописать изменение в конец `pending`, если такого ключа там ещё нет. Одно выражение — одна запись базы. */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})\n then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: number) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/**\n * Сценарии в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим с хранилищем\n * чатов. Несколько копий сервера на одном файле делят таймеры арендой: каждое изменение — одно условное `update`\n * или короткая транзакция с захватом записи (`immediate`).\n */\nexport function createSqliteFlowStore(filePath: string, migrationsFolder: string): SqliteFlowStore {\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"busy_timeout = 5000\");\n const db = drizzle(sqlite);\n migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });\n\n const getChat = (chatId: string) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n\n return {\n async pinChat(input) {\n db.insert(flowChats)\n .values({\n chatId: input.chatId,\n ownerId: input.ownerId,\n flowId: input.flowId,\n versionId: input.versionId,\n stateId: input.stateId,\n pending: [],\n timeZone: input.timeZone,\n ...(input.placement && { placement: input.placement }),\n updatedAt: toMs(input.at),\n })\n .onConflictDoNothing()\n .run();\n return toRecord(getChat(input.chatId)!);\n },\n async getChat(chatId) {\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n const row = db\n .update(flowChats)\n .set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async addPending(chatId, change) {\n // Прочитать и дописать под захватом записи: две копии с одним ключом не дают двух записей.\n const add = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n const next = row && pendingAdded(row.pending, change);\n if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();\n });\n add.immediate();\n },\n async takePending(chatId) {\n // Прочитать и очистить под захватом записи: вторая копия ждёт и видит уже пустой список (кроме применённых в срок).\n const take = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n if (!row || row.pending.length === 0) return [];\n db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();\n return row.pending;\n });\n return take.immediate();\n },\n\n async setDue(chatId, due, options) {\n db.update(flowChats)\n .set({\n dueAt: due ? toMs(due.at) : null,\n dueExitId: due?.exitId ?? null,\n dueBranchHead: due?.branchHead ?? null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined))\n .run();\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n const nowMs = toMs(now);\n // Выбор и аренда — одно `update`: из двух копий, пришедших одновременно, строку получит только первая.\n const due = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowMs))\n .orderBy(asc(flowChats.dueAt), asc(flowChats.chatId))\n .limit(limit);\n const rows = db\n .update(flowChats)\n .set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs })\n .where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs)))\n .returning()\n .all();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n db.update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .run();\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n const renewed = db\n .update(flowChats)\n .set({ leaseUntil: toMs(now) + leaseMs })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .returning({ chatId: flowChats.chatId })\n .all();\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n const inserted = db\n .insert(flowVersions)\n .values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash })\n .all();\n if (inserted.length > 0) return \"new\";\n const saved = db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId)))\n .get();\n return saved?.hash === input.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n const row = db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)))\n .get();\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n const rows = db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId)\n .all();\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async listFlowChats({ flowId, exceptVersionId, chatIds }) {\n if (chatIds?.length === 0) return [];\n const rows = db\n .select({\n chatId: flowChats.chatId,\n ownerId: flowChats.ownerId,\n flowId: flowChats.flowId,\n versionId: flowChats.versionId,\n stateId: flowChats.stateId,\n endedAt: flowChats.endedAt,\n endReason: flowChats.endReason,\n })\n .from(flowChats)\n .where(\n and(\n eq(flowChats.flowId, flowId),\n exceptVersionId === undefined ? undefined : ne(flowChats.versionId, exceptVersionId),\n chatIds === undefined ? undefined : inArray(flowChats.chatId, chatIds),\n ),\n )\n .orderBy(asc(flowChats.chatId))\n .all();\n return rows.map((row) => ({ ...row, endedAt: row.endedAt === null ? null : toIso(row.endedAt) }));\n },\n async migrateBatch(input) {\n const change = migratePending(input);\n if (input.chatIds?.length === 0) return { moved: [] };\n const notYetMoved = and(\n eq(flowChats.flowId, input.flowId),\n ne(flowChats.versionId, input.toVersionId),\n input.chatIds === undefined ? undefined : inArray(flowChats.chatId, input.chatIds),\n );\n const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);\n const moved = db\n .update(flowChats)\n .set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId })\n .all();\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();\n return rows\n .map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };\n db.insert(flowClientCards)\n .values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry })\n .run();\n },\n async forgetCard(ownerId, fieldId) {\n db.delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)))\n .run();\n },\n\n async endChat(chatId, { reason, at }) {\n db.update(flowChats)\n .set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)))\n .run();\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async reopenChat(chatId, { endedAt, at }) {\n const row = db\n .update(flowChats)\n .set({ endedAt: null, endReason: null, reopenedAt: toMs(at) })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.endedAt, toMs(endedAt))))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n const row = db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : toMs(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDayClosed(chatId, day) {\n db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDelivery(chatId, delivery) {\n const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };\n db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async readTimeZone(ownerId) {\n return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n db.insert(flowPeople)\n .values({ ownerId, timeZone, updatedAt: toMs(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } })\n .run();\n },\n\n async readSideTasks(ownerId, chatId) {\n const rows = db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))))\n .all();\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async forgetSideTasks(ownerId) {\n db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();\n },\n async noteSideTask(input) {\n const asked = input.status === \"asked\";\n const doneAt = asked ? null : toMs(input.at);\n const row = db\n .insert(flowSideTasks)\n .values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` },\n })\n .returning()\n .get();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n const row = db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)))\n .get();\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n const row = db\n .insert(flowFirstMessages)\n .values({ ownerId, day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count })\n .get();\n return row!.count;\n },\n\n async close() {\n if (sqlite.open) sqlite.close();\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { index, integer, primaryKey, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n// Времена — миллисекунды с 1970 года, JSON — текстом.\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = sqliteTable(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: text(\"pending\", { mode: \"json\" }).$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: integer(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: integer(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: integer(\"updated_at\").notNull(),\n endedAt: integer(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n reopenedAt: integer(\"reopened_at\"),\n pausedUntil: integer(\"paused_until\"),\n windowClosed: integer(\"window_closed\", { mode: \"boolean\" }).notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: text(\"delivery\", { mode: \"json\" }).$type<FlowPendingDelivery>(),\n placement: text(\"placement\", { mode: \"json\" }).$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = sqliteTable(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: text(\"flow\", { mode: \"json\" }).$type<AgentFlow>().notNull(),\n createdAt: integer(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = sqliteTable(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: text(\"value\", { mode: \"json\" }).notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = sqliteTable(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = sqliteTable(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = sqliteTable(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: integer(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":[]}
|
|
@@ -13,8 +13,8 @@ export interface TransitionReason {
|
|
|
13
13
|
}
|
|
14
14
|
/** Почему разговор закончился: конечная нода, срок вышел, человек пропал, канал заблокирован, своя причина приложения (`flow.end`). */
|
|
15
15
|
export type FlowEndReason = "final" | "deadline" | "abandoned" | "blocked" | (string & {});
|
|
16
|
-
/** Как чат попал в состояние: старт сценария,
|
|
17
|
-
export type FlowPathCause = TransitionDecider | "start" | "migrate";
|
|
16
|
+
/** Как чат попал в состояние: старт сценария, переход, перевод на новую версию или открытие заново (`flow.reopen`, с 0.68). */
|
|
17
|
+
export type FlowPathCause = TransitionDecider | "start" | "migrate" | "reopen";
|
|
18
18
|
export interface FlowPathEntry {
|
|
19
19
|
state: string;
|
|
20
20
|
/** Время входа, мс с 1970 года. */
|
|
@@ -79,6 +79,8 @@ export interface FlowRun {
|
|
|
79
79
|
/** Когда разговор закончился. После конца таймеров и касаний нет. */
|
|
80
80
|
endedAt?: number;
|
|
81
81
|
endReason?: FlowEndReason;
|
|
82
|
+
/** Разговор открыт заново (`flow.reopen`): тишина, утреннее и «не завершил» считаются не раньше этого момента. С 0.68. */
|
|
83
|
+
reopenedAt?: number;
|
|
82
84
|
}
|
|
83
85
|
/** Ход «пишет первым» и что было до него. Ответ хода помечен «не доставлено» — счёт возвращается к этому. */
|
|
84
86
|
export interface FlowTouch {
|
|
@@ -9,6 +9,11 @@ export declare function appendPath(path: FlowPathEntry[], entry: FlowPathEntry):
|
|
|
9
9
|
* запоминает, сколько там пробыли; «назад» продолжает отсчёт с этого места, обычный вход — с нуля.
|
|
10
10
|
*/
|
|
11
11
|
export declare function applyTransition(flow: AgentFlow, run: FlowRun, transition: Transition): FlowRun;
|
|
12
|
+
/**
|
|
13
|
+
* Разговор открыт заново в `at` (`flow.reopen`): конца нет, нода та же, ряд касаний — с первого шага от `at`, счёт ходов
|
|
14
|
+
* сервера и переходов подряд — заново; в пути — запись «открыт заново». Дни программы — от исходного старта.
|
|
15
|
+
*/
|
|
16
|
+
export declare function reopenRun<T extends FlowRun>(flow: AgentFlow, run: T, at: number): T;
|
|
12
17
|
/**
|
|
13
18
|
* Пришло сообщение человека: тишина прервана, счёт переходов подряд — заново, ряд касаний — с первого шага.
|
|
14
19
|
* `dayKey` — местная дата дня сценария («ГГГГ-ММ-ДД»), только у сценариев с днями: по нему считаются рамки дня.
|
|
@@ -50,6 +50,11 @@ export interface FlowRuntime {
|
|
|
50
50
|
export declare function reportTransition(handler: ((event: FlowTransitionEvent) => void | Promise<void>) | undefined, event: FlowTransitionEvent, logError: (message: string) => void): Promise<void>;
|
|
51
51
|
/** `FlowRun` из состояния чата; нет или не то — `null`. */
|
|
52
52
|
export declare function storedRun(value: unknown): ChatFlowRun | null;
|
|
53
|
+
/**
|
|
54
|
+
* Разговор открыт заново (`flow.reopen`), а место в состоянии чата ещё «до»: оно догоняет отметку записи — конец снят,
|
|
55
|
+
* ряд касаний с первого шага от открытия. Уже догнало, не открывали или снова закончен — как есть.
|
|
56
|
+
*/
|
|
57
|
+
export declare function withReopen<T extends FlowRun>(flow: AgentFlow, run: T, record: Partial<Pick<FlowChatRecord, "reopenedAt" | "endedAt">> | null | undefined): T;
|
|
53
58
|
/** Где чат сейчас: из состояния ветки, а на пустой ветке — старт сценария. */
|
|
54
59
|
export declare function currentRun(flow: AgentFlow, values: Record<string, unknown> | undefined, now: number): ChatFlowRun;
|
|
55
60
|
/**
|
|
@@ -80,7 +85,7 @@ export declare function recordPlacement(record: Pick<FlowChatRecord, "stateId" |
|
|
|
80
85
|
* Где чат сейчас вне хода: из состояния ветки; до первого хода перенесённого чата — из записи переноса; иначе старт.
|
|
81
86
|
* Так таймеры, `admit` и `readRun` видят перенесённое место, не дожидаясь хода человека.
|
|
82
87
|
*/
|
|
83
|
-
export declare function chatRun(flow: AgentFlow, values: Record<string, unknown> | undefined, record: Pick<FlowChatRecord, "stateId" | "placement"> | null | undefined, now: number, timeZone?: string): ChatFlowRun;
|
|
88
|
+
export declare function chatRun(flow: AgentFlow, values: Record<string, unknown> | undefined, record: (Pick<FlowChatRecord, "stateId" | "placement"> & Partial<Pick<FlowChatRecord, "reopenedAt" | "endedAt">>) | null | undefined, now: number, timeZone?: string): ChatFlowRun;
|
|
84
89
|
/**
|
|
85
90
|
* Пояс человека для шага движка вне подготовки хода, как в `runOutsideTurn`: пояса модуля, без них — из записи чата;
|
|
86
91
|
* неизвестен — `defaultTimeZone`, иначе сервера.
|
|
@@ -58,6 +58,47 @@ export interface FlowEndInput {
|
|
|
58
58
|
/** Итог словами приложения: придёт в `onFinish.outcome`. */
|
|
59
59
|
outcome?: string;
|
|
60
60
|
}
|
|
61
|
+
/** Открыть законченный разговор заново (`flow.reopen`). С 0.68. */
|
|
62
|
+
export interface FlowReopenInput {
|
|
63
|
+
ownerId: string;
|
|
64
|
+
chatId: string;
|
|
65
|
+
/** Почему открыт, словами приложения: придёт в `onReopen.reason`. */
|
|
66
|
+
reason?: string;
|
|
67
|
+
/** Открыть, только если разговор закончился не раньше этого момента; раньше — `ended_too_long_ago`. Политика давности — приложения. */
|
|
68
|
+
endedAfter?: Date;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Почему разговор не открыт: не по сценарию (`not_flow`), не закончен или уже открыт другим вызовом (`not_ended`), конец,
|
|
72
|
+
* который не открывается (`final`, `deadline`, `blocked`), срок программы уже вышел (`deadline_passed`), закончился раньше
|
|
73
|
+
* `endedAfter` (`ended_too_long_ago`).
|
|
74
|
+
*/
|
|
75
|
+
export type FlowReopenRefusal = "not_flow" | "not_ended" | "final" | "deadline" | "blocked" | "deadline_passed" | "ended_too_long_ago";
|
|
76
|
+
export type FlowReopenResult = {
|
|
77
|
+
reopened: true;
|
|
78
|
+
} | {
|
|
79
|
+
reopened: false;
|
|
80
|
+
reason: FlowReopenRefusal;
|
|
81
|
+
};
|
|
82
|
+
/** Разговор открыт заново: приложение возвращает статус «идёт». */
|
|
83
|
+
export interface FlowReopen {
|
|
84
|
+
ownerId: string;
|
|
85
|
+
chatId: string;
|
|
86
|
+
flowId: string;
|
|
87
|
+
versionId: string;
|
|
88
|
+
/** Чем разговор кончился: `abandoned` или своя причина приложения. */
|
|
89
|
+
endReason: FlowEndReason;
|
|
90
|
+
endedAt: Date;
|
|
91
|
+
/** Причина открытия из `flow.reopen`. */
|
|
92
|
+
reason?: string;
|
|
93
|
+
/** День программы сейчас, от исходного старта; без «Дней и времени» — `null`. */
|
|
94
|
+
day: number | null;
|
|
95
|
+
/** Нода, где разговор продолжается: та, где кончился. */
|
|
96
|
+
state: string;
|
|
97
|
+
tags: string[];
|
|
98
|
+
/** Место после открытия: путь с записью «открыт заново», заметки, счётчики. */
|
|
99
|
+
run: FlowRun;
|
|
100
|
+
at: Date;
|
|
101
|
+
}
|
|
61
102
|
/** Перенести идущий чат сразу, без хода: сценарий и место — как у `loadFlow`. */
|
|
62
103
|
export type FlowAdoptInput = LoadedFlow & {
|
|
63
104
|
ownerId: string;
|
|
@@ -552,6 +593,8 @@ export interface AgentFlowOptions {
|
|
|
552
593
|
onTransition?: (event: FlowTransitionEvent) => void | Promise<void>;
|
|
553
594
|
/** Разговор закончился (`final`, `deadline`, `abandoned`, `blocked`). Ошибка приложения не ломает сценарий. */
|
|
554
595
|
onFinish?: (event: FlowFinish) => void | Promise<void>;
|
|
596
|
+
/** Разговор открыт заново (`flow.reopen`). Ошибка приложения не ломает сценарий. С 0.68. */
|
|
597
|
+
onReopen?: (event: FlowReopen) => void | Promise<void>;
|
|
555
598
|
/** Карточка клиента в хранилище приложения. Нет — таблица модуля. */
|
|
556
599
|
clientCard?: ClientCardStore;
|
|
557
600
|
/**
|
|
@@ -605,6 +648,13 @@ export interface AgentFlowHandle {
|
|
|
605
648
|
end(input: FlowEndInput): Promise<{
|
|
606
649
|
ended: boolean;
|
|
607
650
|
}>;
|
|
651
|
+
/**
|
|
652
|
+
* Открыть законченный разговор заново: «не завершил» (`abandoned`) или своя причина приложения (`flow.end`). Нода,
|
|
653
|
+
* история, память и `chatId` те же; ряд касаний — с первого шага от открытия; дни и срок программы — от исходного
|
|
654
|
+
* старта. Событие `onReopen`, запись «reopen» в пути, сроки поставлены. Не открывается — `reopened: false` и причина,
|
|
655
|
+
* ничего не меняется. Повтор и гонка копий безопасны: открывает один вызов. С 0.68.
|
|
656
|
+
*/
|
|
657
|
+
reopen(input: FlowReopenInput): Promise<FlowReopenResult>;
|
|
608
658
|
/** Сообщение человека в канале приложения: отвечать, ответить готовой фразой, промолчать. Звать до хода. */
|
|
609
659
|
admit(input: FlowAdmitInput): Promise<FlowAdmitDecision>;
|
|
610
660
|
/** Отвечает живой сотрудник: агент молчит, таймеры и касания стоят. */
|
|
@@ -45,6 +45,8 @@ export interface FlowChatRecord {
|
|
|
45
45
|
/** Разговор закончился: таймеров и касаний больше нет. */
|
|
46
46
|
endedAt: string | null;
|
|
47
47
|
endReason: FlowEndReason | null;
|
|
48
|
+
/** Разговор открыт заново (`flow.reopen`) в этот момент, по часам чата; место догоняет отметку на ближайшем чтении. С 0.68. */
|
|
49
|
+
reopenedAt: string | null;
|
|
48
50
|
/** Пауза «отвечает живой сотрудник» до этого момента; таймеры не берутся. */
|
|
49
51
|
pausedUntil: string | null;
|
|
50
52
|
/** Канал приложения закрыл окно: первым не писать до сообщения человека. */
|
|
@@ -234,6 +236,14 @@ export interface FlowStore {
|
|
|
234
236
|
reason: FlowEndReason;
|
|
235
237
|
at: string;
|
|
236
238
|
}): Promise<FlowChatRecord | null>;
|
|
239
|
+
/**
|
|
240
|
+
* Открыть законченный разговор заново: только если он всё ещё закончен в `endedAt` (что прочитал вызывающий). Снимает
|
|
241
|
+
* конец, ставит `reopenedAt`. Не тот конец, не закончен или чата нет — `null`: из двух копий открывает одна. С 0.68.
|
|
242
|
+
*/
|
|
243
|
+
reopenChat(chatId: string, input: {
|
|
244
|
+
endedAt: string;
|
|
245
|
+
at: string;
|
|
246
|
+
}): Promise<FlowChatRecord | null>;
|
|
237
247
|
/** `null` — снять паузу. */
|
|
238
248
|
setPause(chatId: string, until: string | null): Promise<FlowChatRecord | null>;
|
|
239
249
|
setWindowClosed(chatId: string, closed: boolean): Promise<void>;
|