@ykurilov/agent-chat-background 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.
Files changed (45) hide show
  1. package/CHANGELOG.md +376 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -2
  4. package/dist/chunk-4RA3JT6K.js +9 -0
  5. package/dist/chunk-4RA3JT6K.js.map +1 -0
  6. package/dist/chunk-4XFSUFM6.js +53 -0
  7. package/dist/chunk-4XFSUFM6.js.map +1 -0
  8. package/dist/index.js +756 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/labels.js +14 -0
  11. package/dist/labels.js.map +1 -0
  12. package/dist/postgres.js +212 -0
  13. package/dist/postgres.js.map +1 -0
  14. package/dist/sqlite.js +184 -0
  15. package/dist/sqlite.js.map +1 -0
  16. package/dist/types/background-tasks.d.ts +18 -0
  17. package/dist/types/background-tool.d.ts +16 -0
  18. package/dist/types/helper-task.d.ts +17 -0
  19. package/dist/types/index.d.ts +5 -0
  20. package/dist/types/keyed-lock.d.ts +3 -0
  21. package/dist/types/labels.d.ts +10 -0
  22. package/dist/types/postgres.d.ts +9 -0
  23. package/dist/types/sqlite.d.ts +8 -0
  24. package/dist/types/store/memory.d.ts +3 -0
  25. package/dist/types/store/migration-settings.d.ts +4 -0
  26. package/dist/types/store/postgres-schema.d.ts +406 -0
  27. package/dist/types/store/postgres.d.ts +12 -0
  28. package/dist/types/store/sqlite-schema.d.ts +431 -0
  29. package/dist/types/store/sqlite.d.ts +6 -0
  30. package/dist/types/store/task-store.d.ts +23 -0
  31. package/dist/types/task-delivery.d.ts +24 -0
  32. package/dist/types/task-runner.d.ts +51 -0
  33. package/dist/types/task-tools.d.ts +13 -0
  34. package/dist/types/types.d.ts +97 -0
  35. package/drizzle/postgres/0000_init.sql +28 -0
  36. package/drizzle/postgres/0001_delivery_key.sql +1 -0
  37. package/drizzle/postgres/meta/0000_snapshot.json +229 -0
  38. package/drizzle/postgres/meta/0001_snapshot.json +235 -0
  39. package/drizzle/postgres/meta/_journal.json +20 -0
  40. package/drizzle/sqlite/0000_init.sql +28 -0
  41. package/drizzle/sqlite/0001_delivery_key.sql +1 -0
  42. package/drizzle/sqlite/meta/0000_snapshot.json +208 -0
  43. package/drizzle/sqlite/meta/0001_snapshot.json +215 -0
  44. package/drizzle/sqlite/meta/_journal.json +20 -0
  45. package/package.json +75 -3
@@ -0,0 +1,431 @@
1
+ import type { HelperProgress, TaskStatus } from "@ykurilov/agent-chat-core";
2
+ import type { ModelMessage } from "ai";
3
+ export declare const backgroundTasks: import("drizzle-orm/sqlite-core").SQLiteTableWithColumns<{
4
+ name: "background_tasks";
5
+ schema: undefined;
6
+ columns: {
7
+ id: import("drizzle-orm/sqlite-core").SQLiteColumn<{
8
+ name: "id";
9
+ tableName: "background_tasks";
10
+ dataType: "string";
11
+ columnType: "SQLiteText";
12
+ data: string;
13
+ driverParam: string;
14
+ notNull: true;
15
+ hasDefault: false;
16
+ isPrimaryKey: true;
17
+ isAutoincrement: false;
18
+ hasRuntimeDefault: false;
19
+ enumValues: [string, ...string[]];
20
+ baseColumn: never;
21
+ identity: undefined;
22
+ generated: undefined;
23
+ }, {}, {
24
+ length: number | undefined;
25
+ }>;
26
+ ownerId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
27
+ name: "owner_id";
28
+ tableName: "background_tasks";
29
+ dataType: "string";
30
+ columnType: "SQLiteText";
31
+ data: string;
32
+ driverParam: string;
33
+ notNull: true;
34
+ hasDefault: false;
35
+ isPrimaryKey: false;
36
+ isAutoincrement: false;
37
+ hasRuntimeDefault: false;
38
+ enumValues: [string, ...string[]];
39
+ baseColumn: never;
40
+ identity: undefined;
41
+ generated: undefined;
42
+ }, {}, {
43
+ length: number | undefined;
44
+ }>;
45
+ chatId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
46
+ name: "chat_id";
47
+ tableName: "background_tasks";
48
+ dataType: "string";
49
+ columnType: "SQLiteText";
50
+ data: string;
51
+ driverParam: string;
52
+ notNull: true;
53
+ hasDefault: false;
54
+ isPrimaryKey: false;
55
+ isAutoincrement: false;
56
+ hasRuntimeDefault: false;
57
+ enumValues: [string, ...string[]];
58
+ baseColumn: never;
59
+ identity: undefined;
60
+ generated: undefined;
61
+ }, {}, {
62
+ length: number | undefined;
63
+ }>;
64
+ messageId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
65
+ name: "message_id";
66
+ tableName: "background_tasks";
67
+ dataType: "string";
68
+ columnType: "SQLiteText";
69
+ data: string;
70
+ driverParam: string;
71
+ notNull: true;
72
+ hasDefault: false;
73
+ isPrimaryKey: false;
74
+ isAutoincrement: false;
75
+ hasRuntimeDefault: false;
76
+ enumValues: [string, ...string[]];
77
+ baseColumn: never;
78
+ identity: undefined;
79
+ generated: undefined;
80
+ }, {}, {
81
+ length: number | undefined;
82
+ }>;
83
+ toolCallId: import("drizzle-orm/sqlite-core").SQLiteColumn<{
84
+ name: "tool_call_id";
85
+ tableName: "background_tasks";
86
+ dataType: "string";
87
+ columnType: "SQLiteText";
88
+ data: string;
89
+ driverParam: string;
90
+ notNull: true;
91
+ hasDefault: false;
92
+ isPrimaryKey: false;
93
+ isAutoincrement: false;
94
+ hasRuntimeDefault: false;
95
+ enumValues: [string, ...string[]];
96
+ baseColumn: never;
97
+ identity: undefined;
98
+ generated: undefined;
99
+ }, {}, {
100
+ length: number | undefined;
101
+ }>;
102
+ channel: import("drizzle-orm/sqlite-core").SQLiteColumn<{
103
+ name: "channel";
104
+ tableName: "background_tasks";
105
+ dataType: "string";
106
+ columnType: "SQLiteText";
107
+ data: "max" | "telegram" | "vk" | "web";
108
+ driverParam: string;
109
+ notNull: true;
110
+ hasDefault: false;
111
+ isPrimaryKey: false;
112
+ isAutoincrement: false;
113
+ hasRuntimeDefault: false;
114
+ enumValues: ["web", "telegram", "vk", "max"];
115
+ baseColumn: never;
116
+ identity: undefined;
117
+ generated: undefined;
118
+ }, {}, {
119
+ length: number | undefined;
120
+ }>;
121
+ kind: import("drizzle-orm/sqlite-core").SQLiteColumn<{
122
+ name: "kind";
123
+ tableName: "background_tasks";
124
+ dataType: "string";
125
+ columnType: "SQLiteText";
126
+ data: "helper" | "tool";
127
+ driverParam: string;
128
+ notNull: true;
129
+ hasDefault: false;
130
+ isPrimaryKey: false;
131
+ isAutoincrement: false;
132
+ hasRuntimeDefault: false;
133
+ enumValues: ["tool", "helper"];
134
+ baseColumn: never;
135
+ identity: undefined;
136
+ generated: undefined;
137
+ }, {}, {
138
+ length: number | undefined;
139
+ }>;
140
+ name: import("drizzle-orm/sqlite-core").SQLiteColumn<{
141
+ name: "name";
142
+ tableName: "background_tasks";
143
+ dataType: "string";
144
+ columnType: "SQLiteText";
145
+ data: string;
146
+ driverParam: string;
147
+ notNull: true;
148
+ hasDefault: false;
149
+ isPrimaryKey: false;
150
+ isAutoincrement: false;
151
+ hasRuntimeDefault: false;
152
+ enumValues: [string, ...string[]];
153
+ baseColumn: never;
154
+ identity: undefined;
155
+ generated: undefined;
156
+ }, {}, {
157
+ length: number | undefined;
158
+ }>;
159
+ input: import("drizzle-orm/sqlite-core").SQLiteColumn<{
160
+ name: "input";
161
+ tableName: "background_tasks";
162
+ dataType: "json";
163
+ columnType: "SQLiteTextJson";
164
+ data: unknown;
165
+ driverParam: string;
166
+ notNull: false;
167
+ hasDefault: false;
168
+ isPrimaryKey: false;
169
+ isAutoincrement: false;
170
+ hasRuntimeDefault: false;
171
+ enumValues: undefined;
172
+ baseColumn: never;
173
+ identity: undefined;
174
+ generated: undefined;
175
+ }, {}, {}>;
176
+ title: import("drizzle-orm/sqlite-core").SQLiteColumn<{
177
+ name: "title";
178
+ tableName: "background_tasks";
179
+ dataType: "string";
180
+ columnType: "SQLiteText";
181
+ data: string;
182
+ driverParam: string;
183
+ notNull: true;
184
+ hasDefault: false;
185
+ isPrimaryKey: false;
186
+ isAutoincrement: false;
187
+ hasRuntimeDefault: false;
188
+ enumValues: [string, ...string[]];
189
+ baseColumn: never;
190
+ identity: undefined;
191
+ generated: undefined;
192
+ }, {}, {
193
+ length: number | undefined;
194
+ }>;
195
+ status: import("drizzle-orm/sqlite-core").SQLiteColumn<{
196
+ name: "status";
197
+ tableName: "background_tasks";
198
+ dataType: "string";
199
+ columnType: "SQLiteText";
200
+ data: TaskStatus;
201
+ driverParam: string;
202
+ notNull: true;
203
+ hasDefault: false;
204
+ isPrimaryKey: false;
205
+ isAutoincrement: false;
206
+ hasRuntimeDefault: false;
207
+ enumValues: [string, ...string[]];
208
+ baseColumn: never;
209
+ identity: undefined;
210
+ generated: undefined;
211
+ }, {}, {
212
+ length: number | undefined;
213
+ $type: TaskStatus;
214
+ }>;
215
+ helperMessages: import("drizzle-orm/sqlite-core").SQLiteColumn<{
216
+ name: "helper_messages";
217
+ tableName: "background_tasks";
218
+ dataType: "json";
219
+ columnType: "SQLiteTextJson";
220
+ data: ModelMessage[];
221
+ driverParam: string;
222
+ notNull: false;
223
+ hasDefault: false;
224
+ isPrimaryKey: false;
225
+ isAutoincrement: false;
226
+ hasRuntimeDefault: false;
227
+ enumValues: undefined;
228
+ baseColumn: never;
229
+ identity: undefined;
230
+ generated: undefined;
231
+ }, {}, {
232
+ $type: ModelMessage[];
233
+ }>;
234
+ progress: import("drizzle-orm/sqlite-core").SQLiteColumn<{
235
+ name: "progress";
236
+ tableName: "background_tasks";
237
+ dataType: "json";
238
+ columnType: "SQLiteTextJson";
239
+ data: HelperProgress;
240
+ driverParam: string;
241
+ notNull: false;
242
+ hasDefault: false;
243
+ isPrimaryKey: false;
244
+ isAutoincrement: false;
245
+ hasRuntimeDefault: false;
246
+ enumValues: undefined;
247
+ baseColumn: never;
248
+ identity: undefined;
249
+ generated: undefined;
250
+ }, {}, {
251
+ $type: HelperProgress;
252
+ }>;
253
+ result: import("drizzle-orm/sqlite-core").SQLiteColumn<{
254
+ name: "result";
255
+ tableName: "background_tasks";
256
+ dataType: "json";
257
+ columnType: "SQLiteTextJson";
258
+ data: unknown;
259
+ driverParam: string;
260
+ notNull: false;
261
+ hasDefault: false;
262
+ isPrimaryKey: false;
263
+ isAutoincrement: false;
264
+ hasRuntimeDefault: false;
265
+ enumValues: undefined;
266
+ baseColumn: never;
267
+ identity: undefined;
268
+ generated: undefined;
269
+ }, {}, {}>;
270
+ error: import("drizzle-orm/sqlite-core").SQLiteColumn<{
271
+ name: "error";
272
+ tableName: "background_tasks";
273
+ dataType: "string";
274
+ columnType: "SQLiteText";
275
+ data: string;
276
+ driverParam: string;
277
+ notNull: false;
278
+ hasDefault: false;
279
+ isPrimaryKey: false;
280
+ isAutoincrement: false;
281
+ hasRuntimeDefault: false;
282
+ enumValues: [string, ...string[]];
283
+ baseColumn: never;
284
+ identity: undefined;
285
+ generated: undefined;
286
+ }, {}, {
287
+ length: number | undefined;
288
+ }>;
289
+ leaseOwner: import("drizzle-orm/sqlite-core").SQLiteColumn<{
290
+ name: "lease_owner";
291
+ tableName: "background_tasks";
292
+ dataType: "string";
293
+ columnType: "SQLiteText";
294
+ data: string;
295
+ driverParam: string;
296
+ notNull: false;
297
+ hasDefault: false;
298
+ isPrimaryKey: false;
299
+ isAutoincrement: false;
300
+ hasRuntimeDefault: false;
301
+ enumValues: [string, ...string[]];
302
+ baseColumn: never;
303
+ identity: undefined;
304
+ generated: undefined;
305
+ }, {}, {
306
+ length: number | undefined;
307
+ }>;
308
+ leaseUntil: import("drizzle-orm/sqlite-core").SQLiteColumn<{
309
+ name: "lease_until";
310
+ tableName: "background_tasks";
311
+ dataType: "number";
312
+ columnType: "SQLiteInteger";
313
+ data: number;
314
+ driverParam: number;
315
+ notNull: false;
316
+ hasDefault: false;
317
+ isPrimaryKey: false;
318
+ isAutoincrement: false;
319
+ hasRuntimeDefault: false;
320
+ enumValues: undefined;
321
+ baseColumn: never;
322
+ identity: undefined;
323
+ generated: undefined;
324
+ }, {}, {}>;
325
+ attempt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
326
+ name: "attempt";
327
+ tableName: "background_tasks";
328
+ dataType: "number";
329
+ columnType: "SQLiteInteger";
330
+ data: number;
331
+ driverParam: number;
332
+ notNull: true;
333
+ hasDefault: true;
334
+ isPrimaryKey: false;
335
+ isAutoincrement: false;
336
+ hasRuntimeDefault: false;
337
+ enumValues: undefined;
338
+ baseColumn: never;
339
+ identity: undefined;
340
+ generated: undefined;
341
+ }, {}, {}>;
342
+ createdAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
343
+ name: "created_at";
344
+ tableName: "background_tasks";
345
+ dataType: "number";
346
+ columnType: "SQLiteInteger";
347
+ data: number;
348
+ driverParam: number;
349
+ notNull: true;
350
+ hasDefault: false;
351
+ isPrimaryKey: false;
352
+ isAutoincrement: false;
353
+ hasRuntimeDefault: false;
354
+ enumValues: undefined;
355
+ baseColumn: never;
356
+ identity: undefined;
357
+ generated: undefined;
358
+ }, {}, {}>;
359
+ startedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
360
+ name: "started_at";
361
+ tableName: "background_tasks";
362
+ dataType: "number";
363
+ columnType: "SQLiteInteger";
364
+ data: number;
365
+ driverParam: number;
366
+ notNull: false;
367
+ hasDefault: false;
368
+ isPrimaryKey: false;
369
+ isAutoincrement: false;
370
+ hasRuntimeDefault: false;
371
+ enumValues: undefined;
372
+ baseColumn: never;
373
+ identity: undefined;
374
+ generated: undefined;
375
+ }, {}, {}>;
376
+ finishedAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
377
+ name: "finished_at";
378
+ tableName: "background_tasks";
379
+ dataType: "number";
380
+ columnType: "SQLiteInteger";
381
+ data: number;
382
+ driverParam: number;
383
+ notNull: false;
384
+ hasDefault: false;
385
+ isPrimaryKey: false;
386
+ isAutoincrement: false;
387
+ hasRuntimeDefault: false;
388
+ enumValues: undefined;
389
+ baseColumn: never;
390
+ identity: undefined;
391
+ generated: undefined;
392
+ }, {}, {}>;
393
+ deliveredAt: import("drizzle-orm/sqlite-core").SQLiteColumn<{
394
+ name: "delivered_at";
395
+ tableName: "background_tasks";
396
+ dataType: "number";
397
+ columnType: "SQLiteInteger";
398
+ data: number;
399
+ driverParam: number;
400
+ notNull: false;
401
+ hasDefault: false;
402
+ isPrimaryKey: false;
403
+ isAutoincrement: false;
404
+ hasRuntimeDefault: false;
405
+ enumValues: undefined;
406
+ baseColumn: never;
407
+ identity: undefined;
408
+ generated: undefined;
409
+ }, {}, {}>;
410
+ deliveryKey: import("drizzle-orm/sqlite-core").SQLiteColumn<{
411
+ name: "delivery_key";
412
+ tableName: "background_tasks";
413
+ dataType: "string";
414
+ columnType: "SQLiteText";
415
+ data: string;
416
+ driverParam: string;
417
+ notNull: false;
418
+ hasDefault: false;
419
+ isPrimaryKey: false;
420
+ isAutoincrement: false;
421
+ hasRuntimeDefault: false;
422
+ enumValues: [string, ...string[]];
423
+ baseColumn: never;
424
+ identity: undefined;
425
+ generated: undefined;
426
+ }, {}, {
427
+ length: number | undefined;
428
+ }>;
429
+ };
430
+ dialect: 'sqlite';
431
+ }>;
@@ -0,0 +1,6 @@
1
+ import type { TaskStore } from "../types.js";
2
+ /**
3
+ * Хранилище задач в файле SQLite. Схема приводится к последней миграции при открытии; файл может быть общим
4
+ * с хранилищем чатов. Две копии сервера на одном файле делят задачи через аренду.
5
+ */
6
+ export declare function createSqliteTaskStore(filePath: string, migrationsFolder: string): TaskStore;
@@ -0,0 +1,23 @@
1
+ import type { TaskStatus } from "@ykurilov/agent-chat-core";
2
+ import type { TaskCondition, TaskPatch, TaskRecord } from "../types.js";
3
+ /**
4
+ * Жизнь задачи в хранилище.
5
+ * - `queued`/`running` — идёт; аренда у копии сервера, которая её ведёт.
6
+ * - `awaiting_approval` и законченные — ждут доставки служебного сообщения; аренда у копии, которая доставляет.
7
+ * - `delivered_at` стоит — чату всё сообщено, аренды нет, задача никому не нужна до следующего события.
8
+ * Поэтому «задаче нужна копия сервера» — ровно `delivered_at IS NULL`.
9
+ */
10
+ export declare const RUNNING_STATUSES: readonly ["queued", "running"];
11
+ export declare const FINISHED_STATUSES: readonly ["done", "failed", "canceled", "interrupted", "timed_out"];
12
+ /** О чём чат получает служебное сообщение. */
13
+ export declare const NOTICE_STATUSES: readonly ["done", "failed", "canceled", "interrupted", "timed_out", "awaiting_approval"];
14
+ export declare const isRunningStatus: (status: TaskStatus) => boolean;
15
+ export declare const isFinishedStatus: (status: TaskStatus) => boolean;
16
+ /** Выполнено ли условие записи для задачи. */
17
+ export declare function meetsCondition(task: TaskRecord, when: TaskCondition | undefined): boolean;
18
+ /** Убирает пустые необязательные поля: запись из любого хранилища выглядит одинаково. */
19
+ export declare function withoutEmptyFields(record: TaskRecord): TaskRecord;
20
+ /** Правка (или вся запись) как пары «поле — значение»: переданный `undefined` стирает поле (`null` в базе). */
21
+ export declare function patchEntries(patch: TaskPatch | TaskRecord): Array<[string, unknown]>;
22
+ /** PostgreSQL не хранит символ NUL ни в тексте, ни в jsonb: убираем его до записи. */
23
+ export declare function withoutNul<T>(value: T): T;
@@ -0,0 +1,24 @@
1
+ import type { AgentNotice, BackgroundPort } from "@ykurilov/agent-chat-core";
2
+ import type { TaskRecord, TaskStore } from "./types.js";
3
+ export interface TaskDelivery {
4
+ /** В чате есть недоставленные итоги, которые держит эта копия: доставить сейчас или, если чат занят, позже. */
5
+ request(ownerId: string, chatId: string): Promise<void>;
6
+ /** Ход чата закончился: если чат ждал доставки — пора. */
7
+ chatFree(ownerId: string, chatId: string): void;
8
+ /** Повтор для всех чатов, где доставка не удалась (обход по таймеру). */
9
+ retryPending(): Promise<void>;
10
+ forget(ownerId: string, chatId: string): void;
11
+ /** Дождаться доставок, которые уже идут. */
12
+ idle(): Promise<void>;
13
+ }
14
+ /**
15
+ * Доставка итогов задач в чат служебными сообщениями. Копия доставляет только задачи со своей арендой. Готовые задачи
16
+ * чата уходят пачками с ключом из ключей задач: повтор после сбоя ядро не повторит.
17
+ */
18
+ export declare function createTaskDelivery(deps: {
19
+ store: TaskStore;
20
+ leaseOwner: string;
21
+ port(): BackgroundPort;
22
+ }): TaskDelivery;
23
+ /** Служебное сообщение о задаче, как его видят модель и экран. */
24
+ export declare function noticeOf(task: TaskRecord): AgentNotice;
@@ -0,0 +1,51 @@
1
+ import type { BackgroundPort, TaskStatus } from "@ykurilov/agent-chat-core";
2
+ import type { TaskDelivery } from "./task-delivery.js";
3
+ import type { TaskPatch, TaskRecord, TaskStore } from "./types.js";
4
+ /** Чем закончился запуск: статус и поля итога. */
5
+ export type TaskOutcome = TaskPatch & {
6
+ status: TaskStatus;
7
+ };
8
+ /** Что запуск задачи получает от бегунка. */
9
+ export interface RunControl {
10
+ signal: AbortSignal;
11
+ /** Короткий текст хода в карточке; частые вызовы не копят записи. */
12
+ reportProgress(text: string): void;
13
+ /** Сохранить шаг (помощник); аренду проверяет перед записью, потерянная аренда обрывает запуск. */
14
+ saveStep(patch: TaskPatch): Promise<void>;
15
+ }
16
+ /** Как вести задачу; строка — почему её вести нельзя (описания больше нет в настройках). */
17
+ export type TaskPlan = {
18
+ work(record: TaskRecord, control: RunControl): Promise<TaskOutcome>;
19
+ rerunAfterRestart: boolean;
20
+ timeoutMs: number;
21
+ } | string;
22
+ /** Новая задача из вызова инструмента. */
23
+ export type NewTask = Pick<TaskRecord, "ownerId" | "chatId" | "messageId" | "toolCallId" | "channel" | "kind" | "name" | "input" | "title">;
24
+ export interface TaskRunner {
25
+ start(task: NewTask, maxRunningPerUser: number): Promise<{
26
+ status: "started";
27
+ taskId: string;
28
+ } | {
29
+ status: "too_many_tasks";
30
+ }>;
31
+ cancel(ownerId: string, chatId: string, taskId: string, by: "human" | "agent"): Promise<"canceled" | "not_found" | "already_finished">;
32
+ answerApproval(ownerId: string, chatId: string, taskId: string, approvalId: string, decision: "allow" | "deny"): Promise<"resumed" | "not_found" | "stale">;
33
+ chatDeleted(ownerId: string, chatId: string): Promise<void>;
34
+ /** Подобрать задачи, чья аренда истекла: хозяин пропал. */
35
+ recoverExpired(): Promise<void>;
36
+ renewLeases(): Promise<void>;
37
+ /** Остановить запуски без записи итога и отпустить аренды: следующий старт подберёт задачи сразу. */
38
+ stop(): Promise<void>;
39
+ }
40
+ /**
41
+ * Бегунок задач. Задача идёт у той копии сервера, что держит аренду; аренда продлевается по таймеру службы. Итог
42
+ * пишется, только если аренда ещё наша. Переходы одной задачи (итог, отмена, ответ на подтверждение) идут по одному.
43
+ */
44
+ export declare function createTaskRunner(deps: {
45
+ store: TaskStore;
46
+ leaseOwner: string;
47
+ leaseMs: number;
48
+ port(): BackgroundPort;
49
+ delivery: TaskDelivery;
50
+ plan(record: TaskRecord): TaskPlan;
51
+ }): TaskRunner;
@@ -0,0 +1,13 @@
1
+ import type { AgentToolsModel } from "@ykurilov/agent-chat-core";
2
+ import type { Tool } from "ai";
3
+ import type { TaskRunner } from "./task-runner.js";
4
+ import type { TaskStore } from "./types.js";
5
+ export declare const CHECK_TASKS = "check_tasks";
6
+ export declare const CANCEL_TASK = "cancel_task";
7
+ /** Постоянный блок системной инструкции: один на все ходы, кэш модели не ломает. */
8
+ export declare function backgroundInstructions(maxRunningPerUser: number): string;
9
+ /** `check_tasks` и `cancel_task` для хода: только задачи этого чата. */
10
+ export declare function taskToolsForTurn(model: AgentToolsModel, service: {
11
+ store: TaskStore;
12
+ runner(): TaskRunner;
13
+ }): Record<string, Tool>;
@@ -0,0 +1,97 @@
1
+ import type { AgentChannel, HelperProgress, TaskStatus } from "@ykurilov/agent-chat-core";
2
+ import type { FlexibleSchema, ModelMessage } from "ai";
3
+ /** Задача, как она лежит в хранилище. Времена — ISO 8601. */
4
+ export interface TaskRecord {
5
+ id: string;
6
+ ownerId: string;
7
+ chatId: string;
8
+ messageId: string;
9
+ toolCallId: string;
10
+ channel: AgentChannel;
11
+ kind: "tool" | "helper";
12
+ /** Имя фонового инструмента или помощника. */
13
+ name: string;
14
+ input?: unknown;
15
+ /** Для карточки и служебного сообщения. */
16
+ title: string;
17
+ status: TaskStatus;
18
+ /** Шаги помощника: с них задача продолжается после перезапуска. */
19
+ helperMessages?: ModelMessage[];
20
+ progress?: HelperProgress;
21
+ result?: unknown;
22
+ error?: string;
23
+ leaseOwner?: string;
24
+ leaseUntil?: string;
25
+ attempt: number;
26
+ createdAt: string;
27
+ startedAt?: string;
28
+ finishedAt?: string;
29
+ /** Когда служебное сообщение о конце задачи попало в чат. */
30
+ deliveredAt?: string;
31
+ /** Ключ служебного сообщения, с которым задачу начали доставлять: повтор после сбоя идёт той же пачкой и с тем же ключом. */
32
+ deliveryKey?: string;
33
+ }
34
+ /** Поля, которые меняются у уже созданной задачи. */
35
+ export type TaskPatch = Partial<Omit<TaskRecord, "id" | "ownerId" | "chatId" | "createdAt">>;
36
+ /** Условие записи: аренда у этой копии, статус из списка, задача ждёт ответа именно на этот вопрос. */
37
+ export interface TaskCondition {
38
+ leaseOwner?: string;
39
+ statuses?: readonly TaskStatus[];
40
+ pendingApprovalId?: string;
41
+ }
42
+ /** Хранилище задач: память, SQLite, Postgres (контракт один). Каждая операция владельца проверяет сама. */
43
+ export interface TaskStore {
44
+ create(task: TaskRecord): Promise<void>;
45
+ get(ownerId: string, taskId: string): Promise<TaskRecord | null>;
46
+ /** `when` — запись одним условным обновлением: условие не выполнено (задачу забрали, статус сменился) — `null`, запись прежняя. */
47
+ update(ownerId: string, taskId: string, patch: TaskPatch, when?: TaskCondition): Promise<TaskRecord | null>;
48
+ listForChat(ownerId: string, chatId: string): Promise<TaskRecord[]>;
49
+ /** Сколько задач человека занимают место: `queued` и `running`. Задача на паузе (`awaiting_approval`) место не занимает (T055). */
50
+ countActive(ownerId: string): Promise<number>;
51
+ /**
52
+ * Берёт задачу в работу, если она свободна: ждёт запуска или аренда истекла. Два сервера не возьмут одну задачу.
53
+ * `null` — не взяли.
54
+ */
55
+ claim(taskId: string, leaseOwner: string, leaseUntil: string, now: string): Promise<TaskRecord | null>;
56
+ /** Продлевает аренду; `false` — задачу уже забрал другой или она закончена. */
57
+ renewLease(taskId: string, leaseOwner: string, leaseUntil: string): Promise<boolean>;
58
+ /** Задачи в работе, чья аренда истекла: хозяин пропал (перезапуск, падение). */
59
+ listExpired(now: string): Promise<TaskRecord[]>;
60
+ /** Законченные задачи чата, о которых чат ещё не знает. */
61
+ listUndelivered(ownerId: string, chatId: string): Promise<TaskRecord[]>;
62
+ /**
63
+ * Отмечает доставленной пачку с ключом `deliveryKey`. Задачу, которую тем временем продолжили или отменили (ключ
64
+ * сброшен, статус сменился), не трогает: её ведёт и доставит та копия, что её продолжила или отменила.
65
+ */
66
+ markDelivered(taskIds: readonly string[], deliveryKey: string, at: string): Promise<void>;
67
+ deleteForChat(ownerId: string, chatId: string): Promise<void>;
68
+ close?(): Promise<void>;
69
+ }
70
+ /** Контекст запуска фонового инструмента. */
71
+ export interface BackgroundRunContext {
72
+ ownerId: string;
73
+ chatId: string;
74
+ /** Сигнал отмены: срабатывает по «Отменить», таймауту и закрытию службы. */
75
+ signal: AbortSignal;
76
+ /** Коротко, что делается сейчас; видно в карточке задачи. */
77
+ reportProgress(text: string): void;
78
+ }
79
+ /** Фоновый инструмент: агент запускает его, ход идёт дальше, итог придёт служебным сообщением. */
80
+ export interface BackgroundToolDefinition<Input = unknown, Output = unknown> {
81
+ description: string;
82
+ inputSchema: FlexibleSchema<Input>;
83
+ /** Название задачи для карточки и служебного сообщения. */
84
+ title(input: Input): string;
85
+ run(input: Input, context: BackgroundRunContext): Promise<Output>;
86
+ /** Спрашивается до запуска, как у обычного инструмента. */
87
+ needsApproval?: boolean;
88
+ /** `true` — после перезапуска сервера запустить заново; по умолчанию задача получает «прервана». */
89
+ rerunAfterRestart?: boolean;
90
+ /** Предел времени; по умолчанию общий `timeoutMs` службы. */
91
+ timeoutMs?: number;
92
+ /**
93
+ * Пометки инструмента, как у `tool({ metadata })`. `{ externalContent: true }` — задача приносит чужой текст (веб,
94
+ * письма, файлы): вызов в чате помечен, и живые демонстрации в этом чате не запускаются.
95
+ */
96
+ metadata?: Record<string, unknown>;
97
+ }