@ykurilov/agent-chat-flows 0.0.0-stage → 0.56.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 +261 -0
- package/LICENSE +21 -0
- package/README.md +124 -2
- package/agent-flow.schema.json +1020 -0
- package/dist/chunk-3PTJI2XY.js +100 -0
- package/dist/chunk-3PTJI2XY.js.map +1 -0
- package/dist/chunk-BNMQUNSI.js +9 -0
- package/dist/chunk-BNMQUNSI.js.map +1 -0
- package/dist/chunk-BOV6FJGU.js +1288 -0
- package/dist/chunk-BOV6FJGU.js.map +1 -0
- package/dist/chunk-CKY5KG7N.js +165 -0
- package/dist/chunk-CKY5KG7N.js.map +1 -0
- package/dist/chunk-DCTRMCGV.js +34 -0
- package/dist/chunk-DCTRMCGV.js.map +1 -0
- package/dist/format.js +147 -0
- package/dist/format.js.map +1 -0
- package/dist/index.js +3197 -0
- package/dist/index.js.map +1 -0
- package/dist/postgres.js +387 -0
- package/dist/postgres.js.map +1 -0
- package/dist/react.js +2650 -0
- package/dist/react.js.map +1 -0
- package/dist/sqlite.js +319 -0
- package/dist/sqlite.js.map +1 -0
- package/dist/types/check/app-checks.d.ts +16 -0
- package/dist/types/check/check-flow.d.ts +8 -0
- package/dist/types/check/index.d.ts +2 -0
- package/dist/types/engine/data-conditions.d.ts +10 -0
- package/dist/types/engine/day-frame.d.ts +17 -0
- package/dist/types/engine/first-messages.d.ts +21 -0
- package/dist/types/engine/flow-days.d.ts +39 -0
- package/dist/types/engine/flow-graph.d.ts +26 -0
- package/dist/types/engine/flow-run.d.ts +130 -0
- package/dist/types/engine/index.d.ts +14 -0
- package/dist/types/engine/migrate-run.d.ts +11 -0
- package/dist/types/engine/next-transition.d.ts +40 -0
- package/dist/types/engine/nudges.d.ts +78 -0
- package/dist/types/engine/run-updates.d.ts +37 -0
- package/dist/types/engine/side-questions.d.ts +26 -0
- package/dist/types/engine/side-tasks.d.ts +44 -0
- package/dist/types/engine/targets.d.ts +9 -0
- package/dist/types/engine/timers.d.ts +61 -0
- package/dist/types/engine/zoned-time.d.ts +27 -0
- package/dist/types/format/catalog-events.d.ts +5 -0
- package/dist/types/format/compact-flow.d.ts +8 -0
- package/dist/types/format/flow-format.d.ts +286 -0
- package/dist/types/format/flow-json-schema.d.ts +2 -0
- package/dist/types/format/flow-schema.d.ts +605 -0
- package/dist/types/format/index.d.ts +7 -0
- package/dist/types/format/migrate-flow.d.ts +4 -0
- package/dist/types/format/parse-flow.d.ts +6 -0
- package/dist/types/format.d.ts +3 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/labels/client-card.d.ts +20 -0
- package/dist/types/labels/editor.d.ts +261 -0
- package/dist/types/labels/index.d.ts +14 -0
- package/dist/types/labels/migrate.d.ts +19 -0
- package/dist/types/labels/test-chat.d.ts +28 -0
- package/dist/types/not-yet.d.ts +2 -0
- package/dist/types/postgres.d.ts +10 -0
- package/dist/types/prompt/client-card-block.d.ts +16 -0
- package/dist/types/prompt/flow-rules.d.ts +9 -0
- package/dist/types/prompt/rules/after-end.d.ts +3 -0
- package/dist/types/prompt/rules/client-facts.d.ts +2 -0
- package/dist/types/prompt/rules/day-frame.d.ts +2 -0
- package/dist/types/prompt/rules/link-views.d.ts +4 -0
- package/dist/types/prompt/rules/rule-part.d.ts +3 -0
- package/dist/types/prompt/rules/side-tasks.d.ts +2 -0
- package/dist/types/prompt/rules/time-zone.d.ts +2 -0
- package/dist/types/prompt/state-card.d.ts +14 -0
- package/dist/types/react/client-card/client-card-panel.d.ts +8 -0
- package/dist/types/react/client-card/index.d.ts +2 -0
- package/dist/types/react/client-card/use-client-card.d.ts +19 -0
- package/dist/types/react/client-card-skin.d.ts +27 -0
- package/dist/types/react/editor/board-space.d.ts +51 -0
- package/dist/types/react/editor/board-styles.d.ts +1 -0
- package/dist/types/react/editor/editor-model.d.ts +60 -0
- package/dist/types/react/editor/exit-text.d.ts +11 -0
- package/dist/types/react/editor/flow-board.d.ts +11 -0
- package/dist/types/react/editor/flow-editor.d.ts +13 -0
- package/dist/types/react/editor/flow-simulation.d.ts +44 -0
- package/dist/types/react/editor/index.d.ts +6 -0
- package/dist/types/react/editor/schedule-model.d.ts +47 -0
- package/dist/types/react/editor/side-task-model.d.ts +18 -0
- package/dist/types/react/editor/use-flow-editor.d.ts +120 -0
- package/dist/types/react/flow-editor-skin.d.ts +453 -0
- package/dist/types/react/flow-labels.d.ts +8 -0
- package/dist/types/react/index.d.ts +6 -0
- package/dist/types/react/test-chat/flow-test-chat.d.ts +22 -0
- package/dist/types/react/test-chat/index.d.ts +2 -0
- package/dist/types/react/test-chat/use-flow-test-chat.d.ts +50 -0
- package/dist/types/react.d.ts +3 -0
- package/dist/types/runtime/admit.d.ts +15 -0
- package/dist/types/runtime/agent-flow.d.ts +20 -0
- package/dist/types/runtime/client-card.d.ts +9 -0
- package/dist/types/runtime/client-facts.d.ts +22 -0
- package/dist/types/runtime/close-day-tool.d.ts +3 -0
- package/dist/types/runtime/decisions-client.d.ts +33 -0
- package/dist/types/runtime/extra-tool.d.ts +16 -0
- package/dist/types/runtime/first-message-retry.d.ts +4 -0
- package/dist/types/runtime/first-message-rules.d.ts +55 -0
- package/dist/types/runtime/first-reasons-notice.d.ts +9 -0
- package/dist/types/runtime/flow-delivery.d.ts +53 -0
- package/dist/types/runtime/flow-events.d.ts +126 -0
- package/dist/types/runtime/flow-finish.d.ts +68 -0
- package/dist/types/runtime/flow-known-texts.d.ts +11 -0
- package/dist/types/runtime/flow-signals.d.ts +12 -0
- package/dist/types/runtime/flow-timers.d.ts +55 -0
- package/dist/types/runtime/flow-tools.d.ts +23 -0
- package/dist/types/runtime/flow-types.d.ts +547 -0
- package/dist/types/runtime/held-messages.d.ts +25 -0
- package/dist/types/runtime/index.d.ts +11 -0
- package/dist/types/runtime/judge-context.d.ts +36 -0
- package/dist/types/runtime/judge.d.ts +21 -0
- package/dist/types/runtime/migrate-chats.d.ts +20 -0
- package/dist/types/runtime/note-tools.d.ts +15 -0
- package/dist/types/runtime/pause.d.ts +21 -0
- package/dist/types/runtime/prepare-turn.d.ts +12 -0
- package/dist/types/runtime/side-task-tool.d.ts +5 -0
- package/dist/types/runtime/step-tail.d.ts +11 -0
- package/dist/types/runtime/tail/day-line.d.ts +2 -0
- package/dist/types/runtime/tail/frame-line.d.ts +2 -0
- package/dist/types/runtime/tail/side-tasks-line.d.ts +2 -0
- package/dist/types/runtime/tail/tail-part.d.ts +21 -0
- package/dist/types/runtime/test-clock.d.ts +26 -0
- package/dist/types/runtime/time-zone-tool.d.ts +3 -0
- package/dist/types/runtime/turn-gate.d.ts +8 -0
- package/dist/types/sqlite.d.ts +9 -0
- package/dist/types/store/flow-store-shared.d.ts +20 -0
- package/dist/types/store/flow-store.d.ts +240 -0
- package/dist/types/store/memory.d.ts +3 -0
- package/dist/types/store/migration-settings.d.ts +7 -0
- package/dist/types/store/postgres-schema.d.ts +775 -0
- package/dist/types/store/postgres.d.ts +15 -0
- package/dist/types/store/sqlite-schema.d.ts +824 -0
- package/dist/types/store/sqlite.d.ts +9 -0
- package/drizzle/postgres/0000_init.sql +45 -0
- package/drizzle/postgres/0001_days_pause_side_tasks.sql +21 -0
- package/drizzle/postgres/0002_chat_placement.sql +1 -0
- package/drizzle/postgres/meta/0000_snapshot.json +317 -0
- package/drizzle/postgres/meta/0001_snapshot.json +439 -0
- package/drizzle/postgres/meta/0002_snapshot.json +445 -0
- package/drizzle/postgres/meta/_journal.json +27 -0
- package/drizzle/sqlite/0000_init.sql +44 -0
- package/drizzle/sqlite/0001_days_pause_side_tasks.sql +21 -0
- package/drizzle/sqlite/0002_chat_placement.sql +1 -0
- package/drizzle/sqlite/meta/0000_snapshot.json +297 -0
- package/drizzle/sqlite/meta/0001_snapshot.json +427 -0
- package/drizzle/sqlite/meta/0002_snapshot.json +434 -0
- package/drizzle/sqlite/meta/_journal.json +27 -0
- package/package.json +98 -3
package/dist/sqlite.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FLOWS_MIGRATIONS_TABLE
|
|
3
|
+
} from "./chunk-BNMQUNSI.js";
|
|
4
|
+
import {
|
|
5
|
+
appliedInTime,
|
|
6
|
+
byVersionAndState,
|
|
7
|
+
compareText,
|
|
8
|
+
migratePending,
|
|
9
|
+
pendingAdded
|
|
10
|
+
} from "./chunk-DCTRMCGV.js";
|
|
11
|
+
|
|
12
|
+
// src/sqlite.ts
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
|
|
15
|
+
// src/store/sqlite.ts
|
|
16
|
+
import Database from "better-sqlite3";
|
|
17
|
+
import { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql } from "drizzle-orm";
|
|
18
|
+
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
19
|
+
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
|
|
20
|
+
|
|
21
|
+
// src/store/sqlite-schema.ts
|
|
22
|
+
import { index, integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
23
|
+
var flowChats = sqliteTable(
|
|
24
|
+
"flow_chats",
|
|
25
|
+
{
|
|
26
|
+
chatId: text("chat_id").primaryKey(),
|
|
27
|
+
ownerId: text("owner_id").notNull(),
|
|
28
|
+
flowId: text("flow_id").notNull(),
|
|
29
|
+
versionId: text("version_id").notNull(),
|
|
30
|
+
stateId: text("state_id").notNull(),
|
|
31
|
+
pending: text("pending", { mode: "json" }).$type().notNull().default([]),
|
|
32
|
+
dueAt: integer("due_at"),
|
|
33
|
+
dueExitId: text("due_exit_id"),
|
|
34
|
+
dueBranchHead: text("due_branch_head"),
|
|
35
|
+
timeZone: text("time_zone").notNull(),
|
|
36
|
+
leaseUntil: integer("lease_until"),
|
|
37
|
+
leaseOwner: text("lease_owner"),
|
|
38
|
+
updatedAt: integer("updated_at").notNull(),
|
|
39
|
+
endedAt: integer("ended_at"),
|
|
40
|
+
endReason: text("end_reason").$type(),
|
|
41
|
+
pausedUntil: integer("paused_until"),
|
|
42
|
+
windowClosed: integer("window_closed", { mode: "boolean" }).notNull().default(false),
|
|
43
|
+
dayClosedOn: text("day_closed_on"),
|
|
44
|
+
delivery: text("delivery", { mode: "json" }).$type(),
|
|
45
|
+
placement: text("placement", { mode: "json" }).$type()
|
|
46
|
+
},
|
|
47
|
+
(table) => [
|
|
48
|
+
index("flow_chats_due_idx").on(table.dueAt),
|
|
49
|
+
index("flow_chats_flow_state_idx").on(table.flowId, table.versionId, table.stateId),
|
|
50
|
+
index("flow_chats_owner_idx").on(table.ownerId)
|
|
51
|
+
]
|
|
52
|
+
);
|
|
53
|
+
var flowVersions = sqliteTable(
|
|
54
|
+
"flow_versions",
|
|
55
|
+
{
|
|
56
|
+
flowId: text("flow_id").notNull(),
|
|
57
|
+
versionId: text("version_id").notNull(),
|
|
58
|
+
hash: text("hash").notNull(),
|
|
59
|
+
flow: text("flow", { mode: "json" }).$type().notNull(),
|
|
60
|
+
createdAt: integer("created_at").notNull()
|
|
61
|
+
},
|
|
62
|
+
(table) => [primaryKey({ columns: [table.flowId, table.versionId] })]
|
|
63
|
+
);
|
|
64
|
+
var flowClientCards = sqliteTable(
|
|
65
|
+
"flow_client_cards",
|
|
66
|
+
{
|
|
67
|
+
ownerId: text("owner_id").notNull(),
|
|
68
|
+
fieldId: text("field_id").notNull(),
|
|
69
|
+
value: text("value", { mode: "json" }).notNull(),
|
|
70
|
+
updatedAt: integer("updated_at").notNull(),
|
|
71
|
+
chatId: text("chat_id").notNull(),
|
|
72
|
+
flowId: text("flow_id").notNull()
|
|
73
|
+
},
|
|
74
|
+
(table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })]
|
|
75
|
+
);
|
|
76
|
+
var flowFirstMessages = sqliteTable(
|
|
77
|
+
"flow_first_messages",
|
|
78
|
+
{
|
|
79
|
+
ownerId: text("owner_id").notNull(),
|
|
80
|
+
day: text("day").notNull(),
|
|
81
|
+
count: integer("count").notNull().default(0)
|
|
82
|
+
},
|
|
83
|
+
(table) => [primaryKey({ columns: [table.ownerId, table.day] })]
|
|
84
|
+
);
|
|
85
|
+
var flowPeople = sqliteTable("flow_people", {
|
|
86
|
+
ownerId: text("owner_id").primaryKey(),
|
|
87
|
+
timeZone: text("time_zone").notNull(),
|
|
88
|
+
updatedAt: integer("updated_at").notNull()
|
|
89
|
+
});
|
|
90
|
+
var flowSideTasks = sqliteTable(
|
|
91
|
+
"flow_side_tasks",
|
|
92
|
+
{
|
|
93
|
+
ownerId: text("owner_id").notNull(),
|
|
94
|
+
taskId: text("task_id").notNull(),
|
|
95
|
+
chatId: text("chat_id").notNull().default(""),
|
|
96
|
+
asks: integer("asks").notNull().default(0),
|
|
97
|
+
doneAt: integer("done_at")
|
|
98
|
+
},
|
|
99
|
+
(table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })]
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// src/store/sqlite.ts
|
|
103
|
+
var toMs = (iso) => Date.parse(iso);
|
|
104
|
+
var toIso = (ms) => new Date(ms).toISOString();
|
|
105
|
+
function toRecord(row) {
|
|
106
|
+
return {
|
|
107
|
+
chatId: row.chatId,
|
|
108
|
+
ownerId: row.ownerId,
|
|
109
|
+
flowId: row.flowId,
|
|
110
|
+
versionId: row.versionId,
|
|
111
|
+
stateId: row.stateId,
|
|
112
|
+
pending: row.pending,
|
|
113
|
+
dueAt: row.dueAt === null ? null : toIso(row.dueAt),
|
|
114
|
+
dueExitId: row.dueExitId,
|
|
115
|
+
dueBranchHead: row.dueBranchHead,
|
|
116
|
+
timeZone: row.timeZone,
|
|
117
|
+
updatedAt: toIso(row.updatedAt),
|
|
118
|
+
endedAt: row.endedAt === null ? null : toIso(row.endedAt),
|
|
119
|
+
endReason: row.endReason,
|
|
120
|
+
pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),
|
|
121
|
+
windowClosed: row.windowClosed,
|
|
122
|
+
dayClosedOn: row.dayClosedOn,
|
|
123
|
+
delivery: row.delivery,
|
|
124
|
+
placement: row.placement
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var toSideTask = (row) => ({
|
|
128
|
+
taskId: row.taskId,
|
|
129
|
+
chatId: row.chatId === "" ? null : row.chatId,
|
|
130
|
+
asks: row.asks,
|
|
131
|
+
doneAt: row.doneAt === null ? null : toIso(row.doneAt)
|
|
132
|
+
});
|
|
133
|
+
var pendingWith = (change) => sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})
|
|
134
|
+
then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;
|
|
135
|
+
function mappedStateSql(map) {
|
|
136
|
+
const entries = Object.entries(map);
|
|
137
|
+
if (entries.length === 0) return sql`${flowChats.stateId}`;
|
|
138
|
+
const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);
|
|
139
|
+
return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;
|
|
140
|
+
}
|
|
141
|
+
var dueAndFree = (now) => and(
|
|
142
|
+
lte(flowChats.dueAt, now),
|
|
143
|
+
or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),
|
|
144
|
+
isNull(flowChats.endedAt),
|
|
145
|
+
or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now))
|
|
146
|
+
);
|
|
147
|
+
function createSqliteFlowStore(filePath, migrationsFolder) {
|
|
148
|
+
const sqlite = new Database(filePath);
|
|
149
|
+
sqlite.pragma("journal_mode = WAL");
|
|
150
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
151
|
+
const db = drizzle(sqlite);
|
|
152
|
+
migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });
|
|
153
|
+
const getChat = (chatId) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();
|
|
154
|
+
return {
|
|
155
|
+
async pinChat(input) {
|
|
156
|
+
db.insert(flowChats).values({
|
|
157
|
+
chatId: input.chatId,
|
|
158
|
+
ownerId: input.ownerId,
|
|
159
|
+
flowId: input.flowId,
|
|
160
|
+
versionId: input.versionId,
|
|
161
|
+
stateId: input.stateId,
|
|
162
|
+
pending: [],
|
|
163
|
+
timeZone: input.timeZone,
|
|
164
|
+
...input.placement && { placement: input.placement },
|
|
165
|
+
updatedAt: toMs(input.at)
|
|
166
|
+
}).onConflictDoNothing().run();
|
|
167
|
+
return toRecord(getChat(input.chatId));
|
|
168
|
+
},
|
|
169
|
+
async getChat(chatId) {
|
|
170
|
+
const row = getChat(chatId);
|
|
171
|
+
return row ? toRecord(row) : null;
|
|
172
|
+
},
|
|
173
|
+
async updateChat(chatId, patch, at) {
|
|
174
|
+
const row = db.update(flowChats).set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) }).where(eq(flowChats.chatId, chatId)).returning().get();
|
|
175
|
+
return row ? toRecord(row) : null;
|
|
176
|
+
},
|
|
177
|
+
async deleteChat(chatId) {
|
|
178
|
+
db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();
|
|
179
|
+
},
|
|
180
|
+
async addPending(chatId, change) {
|
|
181
|
+
const add = sqlite.transaction(() => {
|
|
182
|
+
const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();
|
|
183
|
+
const next = row && pendingAdded(row.pending, change);
|
|
184
|
+
if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();
|
|
185
|
+
});
|
|
186
|
+
add.immediate();
|
|
187
|
+
},
|
|
188
|
+
async takePending(chatId) {
|
|
189
|
+
const take = sqlite.transaction(() => {
|
|
190
|
+
const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();
|
|
191
|
+
if (!row || row.pending.length === 0) return [];
|
|
192
|
+
db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();
|
|
193
|
+
return row.pending;
|
|
194
|
+
});
|
|
195
|
+
return take.immediate();
|
|
196
|
+
},
|
|
197
|
+
async setDue(chatId, due, options) {
|
|
198
|
+
db.update(flowChats).set({
|
|
199
|
+
dueAt: due ? toMs(due.at) : null,
|
|
200
|
+
dueExitId: due?.exitId ?? null,
|
|
201
|
+
dueBranchHead: due?.branchHead ?? null,
|
|
202
|
+
leaseUntil: null,
|
|
203
|
+
leaseOwner: null
|
|
204
|
+
}).where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : void 0)).run();
|
|
205
|
+
},
|
|
206
|
+
async leaseDue({ now, owner, leaseMs, limit }) {
|
|
207
|
+
const nowMs = toMs(now);
|
|
208
|
+
const due = db.select({ chatId: flowChats.chatId }).from(flowChats).where(dueAndFree(nowMs)).orderBy(asc(flowChats.dueAt), asc(flowChats.chatId)).limit(limit);
|
|
209
|
+
const rows = db.update(flowChats).set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs }).where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs))).returning().all();
|
|
210
|
+
return rows.map(toRecord).sort((left, right) => compareText(left.dueAt, right.dueAt) || compareText(left.chatId, right.chatId));
|
|
211
|
+
},
|
|
212
|
+
async releaseLease(chatId, owner) {
|
|
213
|
+
db.update(flowChats).set({ leaseOwner: null, leaseUntil: null }).where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner))).run();
|
|
214
|
+
},
|
|
215
|
+
async renewLease({ chatId, owner, now, leaseMs }) {
|
|
216
|
+
const renewed = db.update(flowChats).set({ leaseUntil: toMs(now) + leaseMs }).where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner))).returning({ chatId: flowChats.chatId }).all();
|
|
217
|
+
return renewed.length > 0;
|
|
218
|
+
},
|
|
219
|
+
async saveVersion(input) {
|
|
220
|
+
const inserted = db.insert(flowVersions).values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) }).onConflictDoNothing().returning({ hash: flowVersions.hash }).all();
|
|
221
|
+
if (inserted.length > 0) return "new";
|
|
222
|
+
const saved = db.select({ hash: flowVersions.hash }).from(flowVersions).where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId))).get();
|
|
223
|
+
return saved?.hash === input.hash ? "same" : "conflict";
|
|
224
|
+
},
|
|
225
|
+
async getVersion(flowId, versionId) {
|
|
226
|
+
const row = db.select().from(flowVersions).where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId))).get();
|
|
227
|
+
return row ? { ...row, createdAt: toIso(row.createdAt) } : null;
|
|
228
|
+
},
|
|
229
|
+
async countChatsByState({ flowId }) {
|
|
230
|
+
const rows = db.select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql`count(*)` }).from(flowChats).where(eq(flowChats.flowId, flowId)).groupBy(flowChats.versionId, flowChats.stateId).all();
|
|
231
|
+
return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);
|
|
232
|
+
},
|
|
233
|
+
async migrateBatch(input) {
|
|
234
|
+
const change = migratePending(input);
|
|
235
|
+
const notYetMoved = and(eq(flowChats.flowId, input.flowId), ne(flowChats.versionId, input.toVersionId));
|
|
236
|
+
const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);
|
|
237
|
+
const moved = db.update(flowChats).set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) }).where(and(inArray(flowChats.chatId, batch), notYetMoved)).returning({ chatId: flowChats.chatId }).all();
|
|
238
|
+
return { moved: moved.map((row) => row.chatId) };
|
|
239
|
+
},
|
|
240
|
+
async readCard(ownerId) {
|
|
241
|
+
const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();
|
|
242
|
+
return rows.map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId })).sort((left, right) => compareText(left.fieldId, right.fieldId));
|
|
243
|
+
},
|
|
244
|
+
async writeCardField(input) {
|
|
245
|
+
const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };
|
|
246
|
+
db.insert(flowClientCards).values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry }).onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry }).run();
|
|
247
|
+
},
|
|
248
|
+
async forgetCard(ownerId, fieldId) {
|
|
249
|
+
db.delete(flowClientCards).where(and(eq(flowClientCards.ownerId, ownerId), fieldId === "all" ? void 0 : eq(flowClientCards.fieldId, fieldId))).run();
|
|
250
|
+
},
|
|
251
|
+
async endChat(chatId, { reason, at }) {
|
|
252
|
+
db.update(flowChats).set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null }).where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt))).run();
|
|
253
|
+
const row = getChat(chatId);
|
|
254
|
+
return row ? toRecord(row) : null;
|
|
255
|
+
},
|
|
256
|
+
async setPause(chatId, until) {
|
|
257
|
+
const row = db.update(flowChats).set({ pausedUntil: until === null ? null : toMs(until) }).where(eq(flowChats.chatId, chatId)).returning().get();
|
|
258
|
+
return row ? toRecord(row) : null;
|
|
259
|
+
},
|
|
260
|
+
async setWindowClosed(chatId, closed) {
|
|
261
|
+
db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();
|
|
262
|
+
},
|
|
263
|
+
async setDayClosed(chatId, day) {
|
|
264
|
+
db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();
|
|
265
|
+
},
|
|
266
|
+
async setDelivery(chatId, delivery) {
|
|
267
|
+
const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };
|
|
268
|
+
db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();
|
|
269
|
+
},
|
|
270
|
+
async readTimeZone(ownerId) {
|
|
271
|
+
return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;
|
|
272
|
+
},
|
|
273
|
+
async writeTimeZone(ownerId, timeZone, at) {
|
|
274
|
+
db.insert(flowPeople).values({ ownerId, timeZone, updatedAt: toMs(at) }).onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } }).run();
|
|
275
|
+
},
|
|
276
|
+
async readSideTasks(ownerId, chatId) {
|
|
277
|
+
const rows = db.select().from(flowSideTasks).where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, ""), eq(flowSideTasks.chatId, chatId)))).all();
|
|
278
|
+
return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));
|
|
279
|
+
},
|
|
280
|
+
async forgetSideTasks(ownerId) {
|
|
281
|
+
db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();
|
|
282
|
+
},
|
|
283
|
+
async noteSideTask(input) {
|
|
284
|
+
const asked = input.status === "asked";
|
|
285
|
+
const doneAt = asked ? null : toMs(input.at);
|
|
286
|
+
const row = db.insert(flowSideTasks).values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? "", asks: asked ? 1 : 0, doneAt }).onConflictDoUpdate({
|
|
287
|
+
target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],
|
|
288
|
+
set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` }
|
|
289
|
+
}).returning().get();
|
|
290
|
+
return toSideTask(row);
|
|
291
|
+
},
|
|
292
|
+
async countFirstMessages(ownerId, day) {
|
|
293
|
+
const row = db.select({ count: flowFirstMessages.count }).from(flowFirstMessages).where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day))).get();
|
|
294
|
+
return row?.count ?? 0;
|
|
295
|
+
},
|
|
296
|
+
async addFirstMessage(ownerId, day) {
|
|
297
|
+
const row = db.insert(flowFirstMessages).values({ ownerId, day, count: 1 }).onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } }).returning({ count: flowFirstMessages.count }).get();
|
|
298
|
+
return row.count;
|
|
299
|
+
},
|
|
300
|
+
async close() {
|
|
301
|
+
if (sqlite.open) sqlite.close();
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/sqlite.ts
|
|
307
|
+
var MIGRATIONS_FOLDER = fileURLToPath(new URL("../drizzle/sqlite", import.meta.url));
|
|
308
|
+
function sqliteFlowStore(filePath) {
|
|
309
|
+
return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);
|
|
310
|
+
}
|
|
311
|
+
export {
|
|
312
|
+
FLOWS_MIGRATIONS_TABLE,
|
|
313
|
+
flowChats,
|
|
314
|
+
flowClientCards,
|
|
315
|
+
flowFirstMessages,
|
|
316
|
+
flowVersions,
|
|
317
|
+
sqliteFlowStore
|
|
318
|
+
};
|
|
319
|
+
//# sourceMappingURL=sqlite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sqlite.ts","../src/store/sqlite.ts","../src/store/sqlite-schema.ts"],"sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { createSqliteFlowStore, type SqliteFlowStore } from \"./store/sqlite\";\n\nexport type { SqliteFlowStore } from \"./store/sqlite\";\nexport { FLOWS_MIGRATIONS_TABLE } from \"./store/migration-settings\";\nexport { flowChats, flowClientCards, flowFirstMessages, flowVersions } from \"./store/sqlite-schema\";\n\n/** Миграции лежат в пакете рядом с `dist`: `drizzle/sqlite`. */\nconst MIGRATIONS_FOLDER = fileURLToPath(new URL(\"../drizzle/sqlite\", import.meta.url));\n\n/**\n * Сценарии в файле SQLite; файл может быть общим с хранилищем чатов (`createSqliteChatStorage`).\n * Таблицы приводятся к последней миграции при открытии.\n */\nexport function sqliteFlowStore(filePath: string): SqliteFlowStore {\n return createSqliteFlowStore(filePath, MIGRATIONS_FOLDER);\n}\n","import Database from \"better-sqlite3\";\nimport { and, asc, eq, inArray, isNull, lt, lte, ne, or, sql, type SQL } from \"drizzle-orm\";\nimport { drizzle } from \"drizzle-orm/better-sqlite3\";\nimport { migrate } from \"drizzle-orm/better-sqlite3/migrator\";\nimport type { StateMap } from \"../engine/migrate-run\";\nimport type { FlowChatRecord, FlowStore, PendingFlowChange, SideTaskRecord } from \"./flow-store\";\nimport { appliedInTime, byVersionAndState, compareText, migratePending, pendingAdded } from \"./flow-store-shared\";\nimport { FLOWS_MIGRATIONS_TABLE } from \"./migration-settings\";\nimport { flowChats, flowClientCards, flowFirstMessages, flowPeople, flowSideTasks, flowVersions } from \"./sqlite-schema\";\n\n/** Хранилище в файле SQLite: `close` обязателен — файл закрывает приложение при остановке. */\nexport type SqliteFlowStore = FlowStore & Required<Pick<FlowStore, \"close\">>;\n\ntype ChatRow = typeof flowChats.$inferSelect;\n\nconst toMs = (iso: string) => Date.parse(iso);\nconst toIso = (ms: number) => new Date(ms).toISOString();\n\nfunction toRecord(row: ChatRow): FlowChatRecord {\n return {\n chatId: row.chatId,\n ownerId: row.ownerId,\n flowId: row.flowId,\n versionId: row.versionId,\n stateId: row.stateId,\n pending: row.pending,\n dueAt: row.dueAt === null ? null : toIso(row.dueAt),\n dueExitId: row.dueExitId,\n dueBranchHead: row.dueBranchHead,\n timeZone: row.timeZone,\n updatedAt: toIso(row.updatedAt),\n endedAt: row.endedAt === null ? null : toIso(row.endedAt),\n endReason: row.endReason,\n pausedUntil: row.pausedUntil === null ? null : toIso(row.pausedUntil),\n windowClosed: row.windowClosed,\n dayClosedOn: row.dayClosedOn,\n delivery: row.delivery,\n placement: row.placement,\n };\n}\n\ntype SideTaskRow = typeof flowSideTasks.$inferSelect;\nconst toSideTask = (row: SideTaskRow): SideTaskRecord => ({\n taskId: row.taskId,\n chatId: row.chatId === \"\" ? null : row.chatId,\n asks: row.asks,\n doneAt: row.doneAt === null ? null : toIso(row.doneAt),\n});\n\n/** Дописать изменение в конец `pending`, если такого ключа там ещё нет. Одно выражение — одна запись базы. */\nconst pendingWith = (change: PendingFlowChange): SQL =>\n sql`case when exists (select 1 from json_each(${flowChats.pending}) where json_extract(value, '$.key') = ${change.key})\n then ${flowChats.pending} else json_insert(${flowChats.pending}, '$[#]', json(${JSON.stringify(change)})) end`;\n\n/** Состояние по карте перевода: удалённые — в выбранные, остальные как были. */\nfunction mappedStateSql(map: StateMap): SQL {\n const entries = Object.entries(map);\n if (entries.length === 0) return sql`${flowChats.stateId}`;\n const cases = entries.map(([from, to]) => sql`when ${from} then ${to}`);\n return sql`case ${flowChats.stateId} ${sql.join(cases, sql` `)} else ${flowChats.stateId} end`;\n}\n\n/** Таймер наступил, никто его не держит, разговор идёт и не на паузе. Аренда до `lease_until` включительно ещё живая. */\nconst dueAndFree = (now: number) =>\n and(\n lte(flowChats.dueAt, now),\n or(isNull(flowChats.leaseUntil), lt(flowChats.leaseUntil, now)),\n isNull(flowChats.endedAt),\n or(isNull(flowChats.pausedUntil), lte(flowChats.pausedUntil, now)),\n );\n\n/**\n * Сценарии в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим с хранилищем\n * чатов. Несколько копий сервера на одном файле делят таймеры арендой: каждое изменение — одно условное `update`\n * или короткая транзакция с захватом записи (`immediate`).\n */\nexport function createSqliteFlowStore(filePath: string, migrationsFolder: string): SqliteFlowStore {\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"busy_timeout = 5000\");\n const db = drizzle(sqlite);\n migrate(db, { migrationsFolder, migrationsTable: FLOWS_MIGRATIONS_TABLE });\n\n const getChat = (chatId: string) => db.select().from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n\n return {\n async pinChat(input) {\n db.insert(flowChats)\n .values({\n chatId: input.chatId,\n ownerId: input.ownerId,\n flowId: input.flowId,\n versionId: input.versionId,\n stateId: input.stateId,\n pending: [],\n timeZone: input.timeZone,\n ...(input.placement && { placement: input.placement }),\n updatedAt: toMs(input.at),\n })\n .onConflictDoNothing()\n .run();\n return toRecord(getChat(input.chatId)!);\n },\n async getChat(chatId) {\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async updateChat(chatId, patch, at) {\n const row = db\n .update(flowChats)\n .set({ versionId: patch.versionId, stateId: patch.stateId, timeZone: patch.timeZone, placement: patch.placement, updatedAt: toMs(at) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async deleteChat(chatId) {\n db.delete(flowChats).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async addPending(chatId, change) {\n // Прочитать и дописать под захватом записи: две копии с одним ключом не дают двух записей.\n const add = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n const next = row && pendingAdded(row.pending, change);\n if (next) db.update(flowChats).set({ pending: next }).where(eq(flowChats.chatId, chatId)).run();\n });\n add.immediate();\n },\n async takePending(chatId) {\n // Прочитать и очистить под захватом записи: вторая копия ждёт и видит уже пустой список (кроме применённых в срок).\n const take = sqlite.transaction(() => {\n const row = db.select({ pending: flowChats.pending }).from(flowChats).where(eq(flowChats.chatId, chatId)).get();\n if (!row || row.pending.length === 0) return [];\n db.update(flowChats).set({ pending: row.pending.filter(appliedInTime) }).where(eq(flowChats.chatId, chatId)).run();\n return row.pending;\n });\n return take.immediate();\n },\n\n async setDue(chatId, due, options) {\n db.update(flowChats)\n .set({\n dueAt: due ? toMs(due.at) : null,\n dueExitId: due?.exitId ?? null,\n dueBranchHead: due?.branchHead ?? null,\n leaseUntil: null,\n leaseOwner: null,\n })\n .where(and(eq(flowChats.chatId, chatId), options?.unlessDelivery ? isNull(flowChats.delivery) : undefined))\n .run();\n },\n async leaseDue({ now, owner, leaseMs, limit }) {\n const nowMs = toMs(now);\n // Выбор и аренда — одно `update`: из двух копий, пришедших одновременно, строку получит только первая.\n const due = db\n .select({ chatId: flowChats.chatId })\n .from(flowChats)\n .where(dueAndFree(nowMs))\n .orderBy(asc(flowChats.dueAt), asc(flowChats.chatId))\n .limit(limit);\n const rows = db\n .update(flowChats)\n .set({ leaseOwner: owner, leaseUntil: nowMs + leaseMs })\n .where(and(inArray(flowChats.chatId, due), dueAndFree(nowMs)))\n .returning()\n .all();\n return rows.map(toRecord).sort((left, right) => compareText(left.dueAt!, right.dueAt!) || compareText(left.chatId, right.chatId));\n },\n async releaseLease(chatId, owner) {\n db.update(flowChats)\n .set({ leaseOwner: null, leaseUntil: null })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .run();\n },\n async renewLease({ chatId, owner, now, leaseMs }) {\n const renewed = db\n .update(flowChats)\n .set({ leaseUntil: toMs(now) + leaseMs })\n .where(and(eq(flowChats.chatId, chatId), eq(flowChats.leaseOwner, owner)))\n .returning({ chatId: flowChats.chatId })\n .all();\n return renewed.length > 0;\n },\n\n async saveVersion(input) {\n const inserted = db\n .insert(flowVersions)\n .values({ flowId: input.flowId, versionId: input.versionId, hash: input.hash, flow: input.flow, createdAt: toMs(input.at) })\n .onConflictDoNothing()\n .returning({ hash: flowVersions.hash })\n .all();\n if (inserted.length > 0) return \"new\";\n const saved = db\n .select({ hash: flowVersions.hash })\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, input.flowId), eq(flowVersions.versionId, input.versionId)))\n .get();\n return saved?.hash === input.hash ? \"same\" : \"conflict\";\n },\n async getVersion(flowId, versionId) {\n const row = db\n .select()\n .from(flowVersions)\n .where(and(eq(flowVersions.flowId, flowId), eq(flowVersions.versionId, versionId)))\n .get();\n return row ? { ...row, createdAt: toIso(row.createdAt) } : null;\n },\n\n async countChatsByState({ flowId }) {\n const rows = db\n .select({ versionId: flowChats.versionId, stateId: flowChats.stateId, chats: sql<number>`count(*)` })\n .from(flowChats)\n .where(eq(flowChats.flowId, flowId))\n .groupBy(flowChats.versionId, flowChats.stateId)\n .all();\n return rows.map((row) => ({ ...row, chats: Number(row.chats) })).sort(byVersionAndState);\n },\n async migrateBatch(input) {\n const change = migratePending(input);\n const notYetMoved = and(eq(flowChats.flowId, input.flowId), ne(flowChats.versionId, input.toVersionId));\n const batch = db.select({ chatId: flowChats.chatId }).from(flowChats).where(notYetMoved).orderBy(asc(flowChats.chatId)).limit(input.limit);\n const moved = db\n .update(flowChats)\n .set({ versionId: input.toVersionId, stateId: mappedStateSql(input.map), pending: pendingWith(change), updatedAt: toMs(input.at) })\n .where(and(inArray(flowChats.chatId, batch), notYetMoved))\n .returning({ chatId: flowChats.chatId })\n .all();\n return { moved: moved.map((row) => row.chatId) };\n },\n\n async readCard(ownerId) {\n const rows = db.select().from(flowClientCards).where(eq(flowClientCards.ownerId, ownerId)).all();\n return rows\n .map((row) => ({ fieldId: row.fieldId, value: row.value, updatedAt: toIso(row.updatedAt), chatId: row.chatId, flowId: row.flowId }))\n .sort((left, right) => compareText(left.fieldId, right.fieldId));\n },\n async writeCardField(input) {\n const entry = { value: input.value, updatedAt: toMs(input.at), chatId: input.chatId, flowId: input.flowId };\n db.insert(flowClientCards)\n .values({ ownerId: input.ownerId, fieldId: input.fieldId, ...entry })\n .onConflictDoUpdate({ target: [flowClientCards.ownerId, flowClientCards.fieldId], set: entry })\n .run();\n },\n async forgetCard(ownerId, fieldId) {\n db.delete(flowClientCards)\n .where(and(eq(flowClientCards.ownerId, ownerId), fieldId === \"all\" ? undefined : eq(flowClientCards.fieldId, fieldId)))\n .run();\n },\n\n async endChat(chatId, { reason, at }) {\n db.update(flowChats)\n .set({ endedAt: toMs(at), endReason: reason, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null })\n .where(and(eq(flowChats.chatId, chatId), isNull(flowChats.endedAt)))\n .run();\n const row = getChat(chatId);\n return row ? toRecord(row) : null;\n },\n async setPause(chatId, until) {\n const row = db\n .update(flowChats)\n .set({ pausedUntil: until === null ? null : toMs(until) })\n .where(eq(flowChats.chatId, chatId))\n .returning()\n .get();\n return row ? toRecord(row) : null;\n },\n async setWindowClosed(chatId, closed) {\n db.update(flowChats).set({ windowClosed: closed }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDayClosed(chatId, day) {\n db.update(flowChats).set({ dayClosedOn: day }).where(eq(flowChats.chatId, chatId)).run();\n },\n async setDelivery(chatId, delivery) {\n const clean = delivery === null ? null : { ...delivery, retryAt: toIso(toMs(delivery.retryAt)) };\n db.update(flowChats).set({ delivery: clean }).where(eq(flowChats.chatId, chatId)).run();\n },\n\n async readTimeZone(ownerId) {\n return db.select({ timeZone: flowPeople.timeZone }).from(flowPeople).where(eq(flowPeople.ownerId, ownerId)).get()?.timeZone ?? null;\n },\n async writeTimeZone(ownerId, timeZone, at) {\n db.insert(flowPeople)\n .values({ ownerId, timeZone, updatedAt: toMs(at) })\n .onConflictDoUpdate({ target: flowPeople.ownerId, set: { timeZone, updatedAt: toMs(at) } })\n .run();\n },\n\n async readSideTasks(ownerId, chatId) {\n const rows = db\n .select()\n .from(flowSideTasks)\n .where(and(eq(flowSideTasks.ownerId, ownerId), or(eq(flowSideTasks.chatId, \"\"), eq(flowSideTasks.chatId, chatId))))\n .all();\n return rows.map(toSideTask).sort((left, right) => compareText(left.taskId, right.taskId));\n },\n async forgetSideTasks(ownerId) {\n db.delete(flowSideTasks).where(eq(flowSideTasks.ownerId, ownerId)).run();\n },\n async noteSideTask(input) {\n const asked = input.status === \"asked\";\n const doneAt = asked ? null : toMs(input.at);\n const row = db\n .insert(flowSideTasks)\n .values({ ownerId: input.ownerId, taskId: input.taskId, chatId: input.chatId ?? \"\", asks: asked ? 1 : 0, doneAt })\n .onConflictDoUpdate({\n target: [flowSideTasks.ownerId, flowSideTasks.taskId, flowSideTasks.chatId],\n set: asked ? { asks: sql`${flowSideTasks.asks} + 1` } : { doneAt: sql`coalesce(${flowSideTasks.doneAt}, ${doneAt})` },\n })\n .returning()\n .get();\n return toSideTask(row!);\n },\n\n async countFirstMessages(ownerId, day) {\n const row = db\n .select({ count: flowFirstMessages.count })\n .from(flowFirstMessages)\n .where(and(eq(flowFirstMessages.ownerId, ownerId), eq(flowFirstMessages.day, day)))\n .get();\n return row?.count ?? 0;\n },\n async addFirstMessage(ownerId, day) {\n const row = db\n .insert(flowFirstMessages)\n .values({ ownerId, day, count: 1 })\n .onConflictDoUpdate({ target: [flowFirstMessages.ownerId, flowFirstMessages.day], set: { count: sql`${flowFirstMessages.count} + 1` } })\n .returning({ count: flowFirstMessages.count })\n .get();\n return row!.count;\n },\n\n async close() {\n if (sqlite.open) sqlite.close();\n },\n };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowEndReason } from \"../engine/flow-run\";\nimport type { FlowChatPlacement, FlowPendingDelivery, PendingFlowChange } from \"./flow-store\";\nimport { index, integer, primaryKey, sqliteTable, text } from \"drizzle-orm/sqlite-core\";\n\n// Времена — миллисекунды с 1970 года, JSON — текстом.\n\n/** Чаты, которые идут по сценарию. */\nexport const flowChats = sqliteTable(\n \"flow_chats\",\n {\n chatId: text(\"chat_id\").primaryKey(),\n ownerId: text(\"owner_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n stateId: text(\"state_id\").notNull(),\n pending: text(\"pending\", { mode: \"json\" }).$type<PendingFlowChange[]>().notNull().default([]),\n dueAt: integer(\"due_at\"),\n dueExitId: text(\"due_exit_id\"),\n dueBranchHead: text(\"due_branch_head\"),\n timeZone: text(\"time_zone\").notNull(),\n leaseUntil: integer(\"lease_until\"),\n leaseOwner: text(\"lease_owner\"),\n updatedAt: integer(\"updated_at\").notNull(),\n endedAt: integer(\"ended_at\"),\n endReason: text(\"end_reason\").$type<FlowEndReason>(),\n pausedUntil: integer(\"paused_until\"),\n windowClosed: integer(\"window_closed\", { mode: \"boolean\" }).notNull().default(false),\n dayClosedOn: text(\"day_closed_on\"),\n delivery: text(\"delivery\", { mode: \"json\" }).$type<FlowPendingDelivery>(),\n placement: text(\"placement\", { mode: \"json\" }).$type<FlowChatPlacement>(),\n },\n (table) => [\n index(\"flow_chats_due_idx\").on(table.dueAt),\n index(\"flow_chats_flow_state_idx\").on(table.flowId, table.versionId, table.stateId),\n index(\"flow_chats_owner_idx\").on(table.ownerId),\n ],\n);\n\n/** Сохранённые версии сценариев: чат идёт по своей версии, пока его не перевели. */\nexport const flowVersions = sqliteTable(\n \"flow_versions\",\n {\n flowId: text(\"flow_id\").notNull(),\n versionId: text(\"version_id\").notNull(),\n hash: text(\"hash\").notNull(),\n flow: text(\"flow\", { mode: \"json\" }).$type<AgentFlow>().notNull(),\n createdAt: integer(\"created_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.flowId, table.versionId] })],\n);\n\n/** Карточка клиента: поле за полем, переживает чаты. */\nexport const flowClientCards = sqliteTable(\n \"flow_client_cards\",\n {\n ownerId: text(\"owner_id\").notNull(),\n fieldId: text(\"field_id\").notNull(),\n value: text(\"value\", { mode: \"json\" }).notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n chatId: text(\"chat_id\").notNull(),\n flowId: text(\"flow_id\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.fieldId] })],\n);\n\n/** Сколько сообщений агент написал первым за сутки человека. */\nexport const flowFirstMessages = sqliteTable(\n \"flow_first_messages\",\n {\n ownerId: text(\"owner_id\").notNull(),\n day: text(\"day\").notNull(),\n count: integer(\"count\").notNull().default(0),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.day] })],\n);\n\n/** Пояс человека: общий для всех его чатов. */\nexport const flowPeople = sqliteTable(\"flow_people\", {\n ownerId: text(\"owner_id\").primaryKey(),\n timeZone: text(\"time_zone\").notNull(),\n updatedAt: integer(\"updated_at\").notNull(),\n});\n\n/** Попытки попутных задач. `chat_id` пустой — попытки на человека, иначе — на разговор. */\nexport const flowSideTasks = sqliteTable(\n \"flow_side_tasks\",\n {\n ownerId: text(\"owner_id\").notNull(),\n taskId: text(\"task_id\").notNull(),\n chatId: text(\"chat_id\").notNull().default(\"\"),\n asks: integer(\"asks\").notNull().default(0),\n doneAt: integer(\"done_at\"),\n },\n (table) => [primaryKey({ columns: [table.ownerId, table.taskId, table.chatId] })],\n);\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentFlow, FlowIssue, FlowIssueLevel } from "../format/flow-format.js";
|
|
2
|
+
/** Находка приложения: путь в JSON сценария или готовое место. Нет `level` — ошибка, нет `code` — `app_check`. */
|
|
3
|
+
export interface FlowAppIssue {
|
|
4
|
+
message: string;
|
|
5
|
+
level?: FlowIssueLevel;
|
|
6
|
+
code?: string;
|
|
7
|
+
/** `states[2]`, `states[intro].exits[0]`, `anyState.exits[0]`, `clientCard[1]`, `chatNotes[0]`, `sideTasks[0]`. */
|
|
8
|
+
path?: string;
|
|
9
|
+
place?: FlowIssue["place"];
|
|
10
|
+
}
|
|
11
|
+
/** Проверки приложения: одни и те же в `checkFlow(flow, catalog, { checks })` на сервере и в `checks` у `FlowEditor`. */
|
|
12
|
+
export type FlowAppChecks = (flow: AgentFlow) => readonly FlowAppIssue[];
|
|
13
|
+
/** Место находки по пути в JSON сценария; непонятный путь — весь сценарий. */
|
|
14
|
+
export declare function placeOfPath(flow: AgentFlow, path: string): FlowIssue["place"];
|
|
15
|
+
/** Находки приложения как находки `checkFlow`. */
|
|
16
|
+
export declare function appIssues(flow: AgentFlow, found: readonly FlowAppIssue[]): FlowIssue[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type AgentFlow, type FlowCatalog, type FlowIssue } from "../format/flow-format.js";
|
|
2
|
+
import { type FlowAppChecks } from "./app-checks.js";
|
|
3
|
+
export interface CheckFlowOptions {
|
|
4
|
+
/** Свои проверки приложения: их находки идут после встроенных. Те же — в редакторе (`checks` у `FlowEditor`). */
|
|
5
|
+
checks?: FlowAppChecks;
|
|
6
|
+
}
|
|
7
|
+
/** Ошибки и замечания сценария: недостижимые состояния, условия без цели, чужие имена инструментов… */
|
|
8
|
+
export declare function checkFlow(flow: AgentFlow, catalog: FlowCatalog, options?: CheckFlowOptions): FlowIssue[];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DataCondition, type FlowField, type FlowFieldType } from "../format/flow-format.js";
|
|
2
|
+
/**
|
|
3
|
+
* Ответ на вопрос «да/нет»: слово «да»/«нет» (также yes/no/true/false, любой регистр, точка или «!» в конце) или готовое
|
|
4
|
+
* логическое значение. Ничего другого ответом не считается — «может быть» не записывается ни как да, ни как нет.
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseBooleanAnswer(raw: unknown): boolean | undefined;
|
|
7
|
+
/** Значение известно и подходит полю: по типу, а у выбора — ещё и по вариантам. Не подходит — считается неизвестным. */
|
|
8
|
+
export declare function fieldValueFits(field: Pick<FlowField, "type" | "options">, value: unknown): boolean;
|
|
9
|
+
/** Выполнено ли условие по данным: `filled` / `eq` / `ne` / `gt` / `lt` / `contains` по типу поля. */
|
|
10
|
+
export declare function dataConditionHolds(condition: DataCondition, fieldType: FlowFieldType, value: unknown): boolean;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { FlowSchedule } from "../format/flow-format.js";
|
|
2
|
+
import type { FlowRun } from "./flow-run.js";
|
|
3
|
+
/** `normal` — как обычно; `soft` — сворачиваться (строка хвоста); `last` — готовая фраза `hardText`; `closed` — молчать до утра. */
|
|
4
|
+
export type FrameState = "normal" | "soft" | "last" | "closed";
|
|
5
|
+
/** Готовая фраза жёсткой рамки, если автор свою не задал. */
|
|
6
|
+
export declare const DEFAULT_HARD_FRAME_TEXT = "\u041D\u0430 \u0441\u0435\u0433\u043E\u0434\u043D\u044F \u0432\u0441\u0451 \u2014 \u0443\u0442\u0440\u043E\u043C \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u044C\u043D\u043E \u043E\u0442\u0432\u0435\u0447\u0443!";
|
|
7
|
+
/**
|
|
8
|
+
* Состояние рамки дня для `humanMessagesToday`-го сообщения человека за день сценария: на `hardAt`-м — готовая фраза,
|
|
9
|
+
* после — молчание; с `softFrom`-го — сворачиваться. Рамок нет — `normal`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function frameState(humanMessagesToday: number, frame: FlowSchedule["dayFrame"]): FrameState;
|
|
12
|
+
/** Готовая фраза жёсткой рамки: автора, иначе по умолчанию. */
|
|
13
|
+
export declare function hardFrameText(frame: FlowSchedule["dayFrame"]): string;
|
|
14
|
+
/** Сколько сообщений человека уже посчитано за день сценария, который идёт в `at` (`run.dayCounts`). */
|
|
15
|
+
export declare function humanMessagesToday(run: FlowRun, at: number, timeZone: string, schedule: FlowSchedule): number;
|
|
16
|
+
/** Человек закрыл день (`flow_close_day`, запись `dayClosedOn`), и этот день ещё идёт: сегодня первым не пишем. */
|
|
17
|
+
export declare function dayClosedToday(dayClosedOn: string | null, at: number, timeZone: string, schedule: FlowSchedule): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FirstMessagesRules, type FirstMessagesSetting } from "./flow-run.js";
|
|
2
|
+
export { localDay } from "./zoned-time.js";
|
|
3
|
+
export interface FirstMessageInput {
|
|
4
|
+
/** Когда агент хочет написать, мс. */
|
|
5
|
+
at: number;
|
|
6
|
+
timeZone: string;
|
|
7
|
+
/** Сколько уже написал первым за эти сутки человека (`localDay`). */
|
|
8
|
+
sentToday: number;
|
|
9
|
+
}
|
|
10
|
+
/** Настройка приложения поверх значений SDK (→ В10). `false` — правила выключены: `null`, агент пишет первым без ограничений. */
|
|
11
|
+
export declare function resolveFirstMessages(setting: FirstMessagesSetting | undefined): FirstMessagesRules | null;
|
|
12
|
+
/**
|
|
13
|
+
* Можно ли написать первым сейчас; нет — на когда перенести. Тихие часы → их конец. Потолок за сутки исчерпан →
|
|
14
|
+
* следующие сутки человека, после тихих часов.
|
|
15
|
+
*/
|
|
16
|
+
export declare function firstMessageSlot(rules: Omit<FirstMessagesRules, "canWriteNow">, input: FirstMessageInput): {
|
|
17
|
+
allowed: true;
|
|
18
|
+
} | {
|
|
19
|
+
allowed: false;
|
|
20
|
+
retryAt: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type FlowSchedule } from "../format/flow-format.js";
|
|
2
|
+
import { type FlowRun } from "./flow-run.js";
|
|
3
|
+
/** День сценария для хвоста шага, `readRun` и событий. Моменты — мс с 1970 года. */
|
|
4
|
+
export interface FlowDayInfo {
|
|
5
|
+
/** Номер дня, с 1. 0 — вечер позднего старта до первого дня (→ В1). */
|
|
6
|
+
day: number;
|
|
7
|
+
/** Сколько дней в сценарии (`schedule.lengthDays`); нет срока — нет поля. */
|
|
8
|
+
totalDays?: number;
|
|
9
|
+
isLastDay: boolean;
|
|
10
|
+
dayStartedAt: number;
|
|
11
|
+
nextDayStartAt: number;
|
|
12
|
+
}
|
|
13
|
+
/** Местная дата дня сценария (`ГГГГ-ММ-ДД`): ночь и время до начала дня относятся к ближайшему утру. */
|
|
14
|
+
export declare function dayOf(at: number, timeZone: string, schedule: FlowSchedule): string;
|
|
15
|
+
/** Какой день сценария идёт в момент `at`: последний наступивший `dayStart`. Ключ `dayCounts` и номера дня. */
|
|
16
|
+
export declare function dayInProgress(at: number, timeZone: string, schedule: FlowSchedule): string;
|
|
17
|
+
/** Момент начала дня `localDate` в поясе (переход часов учтён). */
|
|
18
|
+
export declare function dayStartAt(localDate: string, timeZone: string, schedule: FlowSchedule): number;
|
|
19
|
+
/** Начало дня сценария, следующего за днём, к которому идёт момент `at` (ночь до начала дня — ещё прошлый день). */
|
|
20
|
+
export declare function nextDayStartAfter(at: number, timeZone: string, schedule: FlowSchedule): number;
|
|
21
|
+
/**
|
|
22
|
+
* Начало первого дня в поясе человека. Записанный якорь в том же поясе — как есть. Пояс сменился (FR-207): если разговор
|
|
23
|
+
* ещё в первом дне — первый день считается заново по новому поясу, позже — номер дня сохраняется, а сроки идут по новым часам.
|
|
24
|
+
*/
|
|
25
|
+
export declare function dayAnchor(run: FlowRun, timeZone: string, schedule: FlowSchedule): number;
|
|
26
|
+
/** Срок таймера «нода длится N дней»: начало дня входа в ноду + N. Вход до первого дня — как вход в первый день. */
|
|
27
|
+
export declare function stateDaysDue(run: FlowRun, days: number, timeZone: string, schedule: FlowSchedule): number;
|
|
28
|
+
/** Срок таймера «наступил день N сценария»: начало дня N от якоря первого дня. Уже наступил — срок в прошлом. */
|
|
29
|
+
export declare function programDayDue(run: FlowRun, day: number, timeZone: string, schedule: FlowSchedule): number;
|
|
30
|
+
/** День сценария сейчас. Якорь — `dayAnchor`; порог позднего старта — `schedule.lateStartHours` (→ В1). */
|
|
31
|
+
export declare function flowDay(run: FlowRun, now: number, timeZone: string, schedule: FlowSchedule): FlowDayInfo;
|
|
32
|
+
/** «Срок вышел»: начало дня `lengthDays + 1`. Срока нет — `null`. */
|
|
33
|
+
export declare function deadlineAt(run: FlowRun, timeZone: string, schedule: FlowSchedule): number | null;
|
|
34
|
+
/** Идёт первый день сценария (или вечер перед ним) и первая ночь ещё не наступила: окно, чтобы узнать город (→ В1). */
|
|
35
|
+
export declare function beforeFirstNight(run: FlowRun, now: number, timeZone: string, schedule: FlowSchedule): boolean;
|
|
36
|
+
/** Тот же `run` с якорем первого дня в поясе человека: записать в состояние чата. Без «Дней и времени» — как есть. */
|
|
37
|
+
export declare function withDayAnchor(run: FlowRun, timeZone: string, schedule: FlowSchedule | undefined): FlowRun;
|
|
38
|
+
/** Местные часы человека «ЧЧ:ММ». */
|
|
39
|
+
export declare function clockText(at: number, timeZone: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AgentFlow, type ClientCardField, type FlowExit, type FlowField, type FlowState } from "../format/flow-format.js";
|
|
2
|
+
export declare function stateById(flow: AgentFlow, id: string | null): FlowState | undefined;
|
|
3
|
+
/** Конечное состояние: условий выхода нет. */
|
|
4
|
+
export declare function isFinal(state: FlowState): boolean;
|
|
5
|
+
/** Касание — состояние с переходом «назад» («Напоминание», «Утреннее касание»). Остальные — основные. */
|
|
6
|
+
export declare function isTouch(state: FlowState): boolean;
|
|
7
|
+
/** Общие переходы действуют везде, кроме конечных и исключённых состояний. */
|
|
8
|
+
export declare function commonApplies(flow: AgentFlow, state: FlowState): boolean;
|
|
9
|
+
export interface ActiveExit {
|
|
10
|
+
exit: FlowExit;
|
|
11
|
+
/** Условие самого состояния, а не общее. */
|
|
12
|
+
own: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** Условия, которые действуют в состоянии, в порядке проверки: свои, потом общие. Общий переход в то же состояние не действует. */
|
|
15
|
+
export declare function activeExits(flow: AgentFlow, state: FlowState): ActiveExit[];
|
|
16
|
+
export type FieldPlace = {
|
|
17
|
+
field: ClientCardField;
|
|
18
|
+
place: "card";
|
|
19
|
+
} | {
|
|
20
|
+
field: FlowField;
|
|
21
|
+
place: "notes";
|
|
22
|
+
};
|
|
23
|
+
/** Поле по `id`: карточка клиента или заметка чата. */
|
|
24
|
+
export declare function fieldById(flow: AgentFlow, id: string): FieldPlace | undefined;
|
|
25
|
+
/** Строки сравниваются без учёта регистра и пробелов по краям: «Да» и «да» — одно. */
|
|
26
|
+
export declare function sameText(left: string, right: string): boolean;
|