@ykurilov/agent-chat-flows 0.63.2 → 0.65.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 +24 -0
- package/dist/{chunk-LT4QELAZ.js → chunk-7VR4YNTJ.js} +121 -14
- package/dist/chunk-7VR4YNTJ.js.map +1 -0
- package/dist/chunk-KRZMJJKG.js +817 -0
- package/dist/chunk-KRZMJJKG.js.map +1 -0
- package/dist/chunk-L7XNC3RB.js +71 -0
- package/dist/chunk-L7XNC3RB.js.map +1 -0
- package/dist/format.js +9 -7
- package/dist/index.js +121 -628
- package/dist/index.js.map +1 -1
- package/dist/react.js +169 -11
- package/dist/react.js.map +1 -1
- package/dist/types/format/index.d.ts +1 -0
- package/dist/types/format/parse-duration.d.ts +5 -0
- package/dist/types/labels/index.d.ts +5 -1
- package/dist/types/labels/notices.d.ts +43 -0
- package/dist/types/labels/test-chat.d.ts +8 -0
- package/dist/types/react/flow-editor-skin.d.ts +6 -0
- package/dist/types/react/index.d.ts +2 -0
- package/dist/types/react/notice-lines.d.ts +14 -0
- package/dist/types/react/test-chat/use-flow-test-chat.d.ts +21 -2
- package/dist/types/react.d.ts +2 -2
- package/dist/types/runtime/first-reasons-notice.d.ts +6 -1
- package/dist/types/runtime/flow-types.d.ts +30 -7
- package/dist/types/runtime/index.d.ts +1 -1
- package/dist/types/runtime/notice-screen.d.ts +5 -0
- package/dist/types/runtime/test-clock.d.ts +7 -4
- package/package.json +4 -4
- package/dist/chunk-H3AN5YUN.js +0 -165
- package/dist/chunk-H3AN5YUN.js.map +0 -1
- package/dist/chunk-KGWXTWBZ.js +0 -100
- package/dist/chunk-KGWXTWBZ.js.map +0 -1
- package/dist/chunk-LT4QELAZ.js.map +0 -1
|
@@ -5,3 +5,4 @@ export { migrateFlow } from "./migrate-flow.js";
|
|
|
5
5
|
export { catalogEventLabel, catalogEventNames } from "./catalog-events.js";
|
|
6
6
|
export { compactFlow } from "./compact-flow.js";
|
|
7
7
|
export { flowJsonSchema } from "./flow-json-schema.js";
|
|
8
|
+
export { parseDuration } from "./parse-duration.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ClientCardLabels } from "./client-card.js";
|
|
2
2
|
import { type FlowEditorLabels } from "./editor.js";
|
|
3
3
|
import { type FlowMigrateLabels } from "./migrate.js";
|
|
4
|
+
import { englishFlowNoticeLabels, russianFlowNoticeLabels, type FlowNoticeLabels } from "./notices.js";
|
|
4
5
|
import { type FlowTestChatLabels } from "./test-chat.js";
|
|
5
6
|
/** Все надписи экранов сценария: по словарю на экран. */
|
|
6
7
|
export interface FlowLabels {
|
|
@@ -8,7 +9,10 @@ export interface FlowLabels {
|
|
|
8
9
|
clientCard: ClientCardLabels;
|
|
9
10
|
migrate: FlowMigrateLabels;
|
|
10
11
|
testChat: FlowTestChatLabels;
|
|
12
|
+
/** Строки пометок «агент написал первым» в чате (`flowNoticeLines`). */
|
|
13
|
+
notices: FlowNoticeLabels;
|
|
11
14
|
}
|
|
12
15
|
export declare const russianFlowLabels: FlowLabels;
|
|
13
16
|
export declare const englishFlowLabels: FlowLabels;
|
|
14
|
-
export type { ClientCardLabels, FlowEditorLabels, FlowMigrateLabels, FlowTestChatLabels };
|
|
17
|
+
export type { ClientCardLabels, FlowEditorLabels, FlowMigrateLabels, FlowNoticeLabels, FlowTestChatLabels };
|
|
18
|
+
export { englishFlowNoticeLabels, russianFlowNoticeLabels };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Строки пометок «агент написал первым» на экране чата (`flowNoticeLines`, право чата `appNotices`): по строке на
|
|
3
|
+
* причину, несколько причин — через запятую в одной строке.
|
|
4
|
+
*/
|
|
5
|
+
export interface FlowNoticeLabels {
|
|
6
|
+
/** Вся строка: «Агент написал первым — {причины}». */
|
|
7
|
+
wroteFirst: (reasons: string) => string;
|
|
8
|
+
/** Разделитель причин. */
|
|
9
|
+
separator: string;
|
|
10
|
+
/** «человек молчал 1 ч 7 мин». */
|
|
11
|
+
silentFor: (duration: string) => string;
|
|
12
|
+
/** Касание при молчании, длительность неизвестна. */
|
|
13
|
+
silence: string;
|
|
14
|
+
morning: string;
|
|
15
|
+
/** Касание по расписанию (`dailyAt`). */
|
|
16
|
+
daily: (at: string) => string;
|
|
17
|
+
/** Ответ на сообщения, оставшиеся без ответа ночью или при лимите. */
|
|
18
|
+
nightMessages: (count: number) => string;
|
|
19
|
+
/** Прощание при «срок вышел». */
|
|
20
|
+
farewell: string;
|
|
21
|
+
/** Срок ноды: `condition` — условие словами, `stateTitle` — куда переход; нет — без них. */
|
|
22
|
+
stateDeadline: (condition: string | undefined, stateTitle: string | undefined) => string;
|
|
23
|
+
/** Общий срок от начала разговора. */
|
|
24
|
+
overallDeadline: (condition: string | undefined, stateTitle: string | undefined) => string;
|
|
25
|
+
/** Условия срока словами. */
|
|
26
|
+
conditions: {
|
|
27
|
+
stateDays: (duration: string) => string;
|
|
28
|
+
programDay: (day: number) => string;
|
|
29
|
+
timeOfDay: (at: string) => string;
|
|
30
|
+
silence: (duration: string) => string;
|
|
31
|
+
inState: (duration: string) => string;
|
|
32
|
+
sinceStart: (duration: string) => string;
|
|
33
|
+
};
|
|
34
|
+
/** Единицы длительности: «1 д 3 ч 20 мин». */
|
|
35
|
+
units: {
|
|
36
|
+
day: string;
|
|
37
|
+
hour: string;
|
|
38
|
+
minute: string;
|
|
39
|
+
};
|
|
40
|
+
lessThanMinute: string;
|
|
41
|
+
}
|
|
42
|
+
export declare const russianFlowNoticeLabels: FlowNoticeLabels;
|
|
43
|
+
export declare const englishFlowNoticeLabels: FlowNoticeLabels;
|
|
@@ -19,6 +19,14 @@ export interface FlowTestChatLabels {
|
|
|
19
19
|
failed: string;
|
|
20
20
|
/** Перемотка к началу следующего дня сценария. */
|
|
21
21
|
nextDay: string;
|
|
22
|
+
/** Перемотка к моменту, когда сценарий сам напишет первым. */
|
|
23
|
+
nextFirstMessage: string;
|
|
24
|
+
/** Впереди сценарий сам ничего не напишет: перематывать к напоминанию не к чему. */
|
|
25
|
+
nothingScheduled: string;
|
|
26
|
+
/** Поле «своё время»: подпись, пример и подсказка на непонятное. */
|
|
27
|
+
customTime: string;
|
|
28
|
+
customTimePlaceholder: string;
|
|
29
|
+
customTimeInvalid: string;
|
|
22
30
|
/** «День 2 из 3», «День 2». */
|
|
23
31
|
day: (day: number, totalDays?: number) => string;
|
|
24
32
|
/** Вечер позднего старта: первый день ещё не начался. */
|
|
@@ -390,6 +390,12 @@ export interface FlowTestChatFrameProps {
|
|
|
390
390
|
day: string | null;
|
|
391
391
|
/** К началу следующего дня сценария; нет — кнопки нет (нет «Дней и времени» или приложение не дало перемотку). */
|
|
392
392
|
onNextDay?(): void;
|
|
393
|
+
/** К моменту, когда сценарий сам напишет первым; нет — кнопки нет (приложение не дало перемотку). */
|
|
394
|
+
onNextFirstMessage?(): void;
|
|
395
|
+
/** Последняя перемотка к напоминанию не нашла, к чему перематывать: впереди сценарий сам ничего не напишет. */
|
|
396
|
+
nothingScheduled?: boolean;
|
|
397
|
+
/** Поле «своё время»: текст вроде «1 ч 30 мин»; `false` — не понял, перемотки не было. Нет — поля нет. */
|
|
398
|
+
onAdvanceText?(text: string): boolean;
|
|
393
399
|
/** Идёт запуск чата или перемотка: кнопки перемотки ждут. */
|
|
394
400
|
busy: boolean;
|
|
395
401
|
/** Чат не открылся или часы не перемотались. */
|
|
@@ -4,3 +4,5 @@ export type * from "./client-card-skin.js";
|
|
|
4
4
|
export * from "./editor/index.js";
|
|
5
5
|
export * from "./client-card/index.js";
|
|
6
6
|
export * from "./test-chat/index.js";
|
|
7
|
+
export { flowNoticeLines } from "./notice-lines.js";
|
|
8
|
+
export type { FlowNoticeInput, FlowNoticeLines } from "./notice-lines.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type FlowNoticeLabels } from "../labels/notices.js";
|
|
2
|
+
/** Пометка приложения, как её отдаёт экран чата (`AppNotice` из `@ykurilov/agent-chat-react`). */
|
|
3
|
+
export interface FlowNoticeInput {
|
|
4
|
+
name: string;
|
|
5
|
+
data: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** Строки пометок для `noticeLines` у `AgentChat`: имя пометки → строка; данные не те — `undefined`, строки нет. */
|
|
8
|
+
export type FlowNoticeLines = Record<string, (notice: FlowNoticeInput) => string | undefined>;
|
|
9
|
+
/**
|
|
10
|
+
* Строки «Агент написал первым — человек молчал 1 ч 7 мин» для пометок сценария (`flow_timer`, `flow_deadline`):
|
|
11
|
+
* `<AgentChat noticeLines={flowNoticeLines()} />`. Видны, только если право чата `appNotices` включено. Подписи —
|
|
12
|
+
* `russianFlowNoticeLabels` (по умолчанию), `englishFlowNoticeLabels` или свои.
|
|
13
|
+
*/
|
|
14
|
+
export declare function flowNoticeLines(labels?: FlowNoticeLabels): FlowNoticeLines;
|
|
@@ -2,6 +2,7 @@ import { type ReactNode } from "react";
|
|
|
2
2
|
import type { FlowDayInfo } from "../../engine/flow-days.js";
|
|
3
3
|
import type { FlowRun } from "../../engine/flow-run.js";
|
|
4
4
|
import type { AgentFlow } from "../../format/flow-format.js";
|
|
5
|
+
import type { FlowNoticeLines } from "../notice-lines.js";
|
|
5
6
|
/** Что тестовому чату нужно от приложения: сервер и экран чата. Редактор не знает, где сервер. */
|
|
6
7
|
export interface FlowTestChatConnection {
|
|
7
8
|
/** Новый тестовый чат по сценарию из редактора: сервер создаёт его с `flowTestChatData()`. */
|
|
@@ -19,12 +20,26 @@ export interface FlowTestChatConnection {
|
|
|
19
20
|
advanceToNextDay?(chatId: string): Promise<{
|
|
20
21
|
timeOffset: number;
|
|
21
22
|
}>;
|
|
23
|
+
/**
|
|
24
|
+
* Промотать часы ровно к моменту, когда сценарий сам напишет первым: на сервере
|
|
25
|
+
* `flow.advanceTestClock({ chatId, to: "next_first_message" })`. Впереди ничего — ошибка с `code: "nothing_scheduled"`
|
|
26
|
+
* (у `FlowTestClockError`; через сеть передайте код). Нет — кнопки «К следующему напоминанию» нет.
|
|
27
|
+
*/
|
|
28
|
+
advanceToNextFirstMessage?(chatId: string): Promise<{
|
|
29
|
+
timeOffset: number;
|
|
30
|
+
}>;
|
|
22
31
|
/** Место чата: на сервере `flow.readRun`. В браузер посетителя место не уходит, доска читает его отдельно. */
|
|
23
32
|
readRun(chatId: string): Promise<(Pick<FlowRun, "state" | "path"> & {
|
|
24
33
|
day?: FlowDayInfo;
|
|
25
34
|
}) | null>;
|
|
26
|
-
/**
|
|
27
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Экран чата приложения: `<AgentChat api=… chatId={chatId} skin=… noticeLines={options.noticeLines} />`.
|
|
37
|
+
* `options.noticeLines` (с 0.65) — строки «Агент написал первым — человек молчал 1 ч 7 мин»: в тестовом чате они
|
|
38
|
+
* включены по умолчанию. Видны, если сервер дал тестовым чатам право `appNotices`.
|
|
39
|
+
*/
|
|
40
|
+
renderChat(chatId: string, options: {
|
|
41
|
+
noticeLines: FlowNoticeLines;
|
|
42
|
+
}): ReactNode;
|
|
28
43
|
}
|
|
29
44
|
export interface FlowTestChatController {
|
|
30
45
|
opened: boolean;
|
|
@@ -45,6 +60,10 @@ export interface FlowTestChatController {
|
|
|
45
60
|
advance(ms: number): void;
|
|
46
61
|
/** К началу следующего дня сценария; `null` — приложение не дало `advanceToNextDay`. */
|
|
47
62
|
nextDay: (() => void) | null;
|
|
63
|
+
/** К следующему сообщению сценария; `null` — приложение не дало `advanceToNextFirstMessage`. */
|
|
64
|
+
nextFirstMessage: (() => void) | null;
|
|
65
|
+
/** Последняя перемотка к напоминанию: впереди сценарий сам ничего не напишет. */
|
|
66
|
+
nothingScheduled: boolean;
|
|
48
67
|
}
|
|
49
68
|
/** Логика тестового чата редактора: открыть, перемотать, начать заново, подсветка состояния. */
|
|
50
69
|
export declare function useFlowTestChat(connection: FlowTestChatConnection, flow: AgentFlow): FlowTestChatController;
|
package/dist/types/react.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export * from "./react/index.js";
|
|
2
|
-
export { englishFlowLabels, russianFlowLabels } from "./labels/index.js";
|
|
3
|
-
export type { ClientCardLabels, FlowEditorLabels, FlowLabels, FlowMigrateLabels, FlowTestChatLabels } from "./labels/index.js";
|
|
2
|
+
export { englishFlowLabels, englishFlowNoticeLabels, russianFlowLabels, russianFlowNoticeLabels } from "./labels/index.js";
|
|
3
|
+
export type { ClientCardLabels, FlowEditorLabels, FlowLabels, FlowMigrateLabels, FlowNoticeLabels, FlowTestChatLabels } from "./labels/index.js";
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { AgentFlow } from "../format/flow-format.js";
|
|
2
2
|
import type { FlowFirstReason } from "../engine/nudges.js";
|
|
3
|
+
import type { FlowRun } from "../engine/flow-run.js";
|
|
3
4
|
import type { FlowNotice } from "./flow-types.js";
|
|
5
|
+
/** Сколько человек молчал к ходу «пишет первым»: от его последнего сообщения по часам чата; не писал — `undefined`. */
|
|
6
|
+
export declare function silentMsOf(run: Pick<FlowRun, "lastHumanAt">, at: number): number | undefined;
|
|
4
7
|
/**
|
|
5
8
|
* Служебное сообщение хода «пишет первым». Только переход ноды — прежнее «Сработало: …» (как в 0.48); есть другие
|
|
6
9
|
* причины — их список и задача «новое и полезное». `data.exitId` — переход, который применит ход; `data.nudgeStep` —
|
|
7
10
|
* шаг ряда касаний после хода (после перехода и утреннего — 0, ряд заново); `data.nudgeStepOwn` — его насчитал свой ряд ноды на N дней (`ownNudge`).
|
|
11
|
+
* Для строки на экране (право `appNotices`, `flowNoticeLines`): `data.silentMs` — сколько человек молчал, `data.exit` —
|
|
12
|
+
* нода, куда ведёт переход, и условие срока. Модель их не видит: текст для неё — `title`.
|
|
8
13
|
*/
|
|
9
|
-
export declare function firstReasonsNotice(flow: AgentFlow, reasons: readonly FlowFirstReason[], exitId: string | null, ownNudge?: boolean): FlowNotice;
|
|
14
|
+
export declare function firstReasonsNotice(flow: AgentFlow, reasons: readonly FlowFirstReason[], exitId: string | null, ownNudge?: boolean, silentMs?: number): FlowNotice;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AfterTurnHandler, AgentNotice, AgentSettings, AgentToolsModel, AskModel, BeforeTurnHandler, Chat, ChatInstructions, ChatService, ModelChoice, RecordUsage, ReplyChecksSetting, StateChangedHandler, StepContextInput, StoredMessage } from "@ykurilov/agent-chat-core";
|
|
1
|
+
import type { AfterTurnHandler, AgentNotice, AgentSettings, AppNoticeViews, AgentToolsModel, AskModel, BeforeTurnHandler, Chat, ChatInstructions, ChatService, ModelChoice, RecordUsage, ReplyChecksSetting, StateChangedHandler, StepContextInput, StoredMessage } from "@ykurilov/agent-chat-core";
|
|
2
2
|
import type { ToolSet } from "ai";
|
|
3
3
|
import type { AgentFlow, FlowAfterEnd, FlowSchedule } from "../format/flow-format.js";
|
|
4
4
|
import type { FirstMessagesSetting, FlowEndReason, FlowRun, Transition } from "../engine/flow-run.js";
|
|
@@ -202,6 +202,8 @@ export interface FlowTimers {
|
|
|
202
202
|
branchHead: string | null;
|
|
203
203
|
failedAt?: number;
|
|
204
204
|
}): Promise<void>;
|
|
205
|
+
/** Слушать, что сработало по таймеру чата (ход «пишет первым», «срок вышел», «не завершил»); возврат — отписка. */
|
|
206
|
+
watch?(chatId: string, onFired: (fired: FlowTestClockReason) => void): () => void;
|
|
205
207
|
}
|
|
206
208
|
export interface FirstMessageRules {
|
|
207
209
|
/** Можно ли написать первым сейчас; нет — когда повторить (мс) или `null`. */
|
|
@@ -372,7 +374,10 @@ export type FlowAdmitDecision = {
|
|
|
372
374
|
| {
|
|
373
375
|
kind: "paused";
|
|
374
376
|
};
|
|
375
|
-
/**
|
|
377
|
+
/**
|
|
378
|
+
* Перемотка тестового чата: на `ms`, до начала следующего дня сценария или ровно к моменту, когда сценарий напишет первым
|
|
379
|
+
* (`next_first_message`, с 0.64).
|
|
380
|
+
*/
|
|
376
381
|
export type FlowTestClockInput = {
|
|
377
382
|
ownerId: string;
|
|
378
383
|
chatId: string;
|
|
@@ -380,9 +385,25 @@ export type FlowTestClockInput = {
|
|
|
380
385
|
ms: number;
|
|
381
386
|
to?: undefined;
|
|
382
387
|
} | {
|
|
383
|
-
to: "next_day_start";
|
|
388
|
+
to: "next_day_start" | "next_first_message";
|
|
384
389
|
ms?: undefined;
|
|
385
390
|
});
|
|
391
|
+
/**
|
|
392
|
+
* Что случилось в момент, куда перемотал `next_first_message`: вид — первая причина хода «пишет первым» (`transition`,
|
|
393
|
+
* `morning`, `silence`…), `deadline` — «срок вышел», `give_up` — «не завершил». `stateId` — нода после срабатывания,
|
|
394
|
+
* `exitId` — выход ноды по таймеру, `reasons` — все причины хода.
|
|
395
|
+
*/
|
|
396
|
+
export interface FlowTestClockReason {
|
|
397
|
+
kind: FlowFirstReason["kind"] | "deadline" | "give_up";
|
|
398
|
+
stateId: string;
|
|
399
|
+
exitId?: string;
|
|
400
|
+
reasons?: FlowFirstReason[];
|
|
401
|
+
}
|
|
402
|
+
/** Итог перемотки тестового чата: новый сдвиг часов, мс; у `next_first_message` — что сработало. */
|
|
403
|
+
export interface FlowTestClockResult {
|
|
404
|
+
timeOffset: number;
|
|
405
|
+
reason?: FlowTestClockReason;
|
|
406
|
+
}
|
|
386
407
|
export interface FlowAdmitInput {
|
|
387
408
|
ownerId: string;
|
|
388
409
|
chatId: string;
|
|
@@ -512,6 +533,8 @@ export interface FlowChatOptions {
|
|
|
512
533
|
* (`flowKnownTexts`) к `knownTexts`: ссылка из первого сообщения или прощания не считается выдуманной. Нет — не трогается.
|
|
513
534
|
*/
|
|
514
535
|
replyChecks?: ReplyChecksSetting;
|
|
536
|
+
/** Свои проекции пометок приложения для экрана (`createChatService({ appNoticeViews })`): `withChat` добавляет к ним сценарные. */
|
|
537
|
+
appNoticeViews?: AppNoticeViews;
|
|
515
538
|
}
|
|
516
539
|
export interface AgentFlowOptions {
|
|
517
540
|
/** Какой сценарий у чата; `null` — чат без сценария. Зовётся, пока чат не закреплён за версией. */
|
|
@@ -600,11 +623,11 @@ export interface AgentFlowHandle {
|
|
|
600
623
|
/**
|
|
601
624
|
* Тестовый чат редактора (создан с `flowTestChatData()`): промотать его часы вперёд на `ms` или до начала следующего
|
|
602
625
|
* дня сценария (`to: "next_day_start"`, по часам человека). Наступивший таймер срабатывает сразу; ответ ждёт конца
|
|
603
|
-
* его хода. Рабочий чат — ошибка.
|
|
626
|
+
* его хода. Рабочий чат — ошибка. `to: "next_first_message"` — ровно к моменту, когда сценарий сам напишет первым
|
|
627
|
+
* (касание, утреннее, выход ноды по сроку, «срок вышел», «не завершил»), с учётом ночи и потолков; впереди ничего —
|
|
628
|
+
* `FlowTestClockError` с кодом `nothing_scheduled`, часы не тронуты.
|
|
604
629
|
*/
|
|
605
|
-
advanceTestClock(input: FlowTestClockInput): Promise<
|
|
606
|
-
timeOffset: number;
|
|
607
|
-
}>;
|
|
630
|
+
advanceTestClock(input: FlowTestClockInput): Promise<FlowTestClockResult>;
|
|
608
631
|
start(): void;
|
|
609
632
|
stop(): Promise<void>;
|
|
610
633
|
/**
|
|
@@ -3,7 +3,7 @@ export { FLOW_SERVICE_BLOCKS } from "./flow-service-blocks.js";
|
|
|
3
3
|
export type * from "./flow-types.js";
|
|
4
4
|
export { DEFAULT_JUDGE_MODEL, DEFAULT_JUDGE_THRESHOLD, DEFAULT_JUDGE_TIMEOUT_MS } from "./judge.js";
|
|
5
5
|
export { DEFAULT_TIMER_LEASE_MS, DEFAULT_TIMER_TICK_MS } from "./flow-timers.js";
|
|
6
|
-
export { flowTestChatData, testChatOffset } from "./test-clock.js";
|
|
6
|
+
export { FlowTestClockError, flowTestChatData, testChatOffset } from "./test-clock.js";
|
|
7
7
|
export { DEFAULT_PAUSE_STAFF_SILENCE_HOURS } from "./pause.js";
|
|
8
8
|
export { FLOW_EXIT_TOOL, FLOW_EXTRA_TOOLS } from "./flow-tools.js";
|
|
9
9
|
export { FLOW_TIME_ZONE_TOOL } from "./time-zone-tool.js";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AppNoticeViews } from "@ykurilov/agent-chat-core";
|
|
2
|
+
/** Данные пометки «пишет первым» для экрана: `reasons`, `silentMs`, `exit`. Не та форма — `undefined`, пометка не уходит. */
|
|
3
|
+
export declare function flowNoticeScreenData(data: unknown): Record<string, unknown> | undefined;
|
|
4
|
+
/** Проекции сценария для `appNoticeViews` службы чата: `withChat` добавляет их сам. */
|
|
5
|
+
export declare const FLOW_APP_NOTICE_VIEWS: AppNoticeViews;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type FlowRun } from "../engine/flow-run.js";
|
|
2
2
|
import { type FlowRuntime } from "./flow-events.js";
|
|
3
|
-
import type { FlowPort, FlowTestClockInput, FlowTimers } from "./flow-types.js";
|
|
3
|
+
import type { FlowPort, FlowTestClockInput, FlowTestClockResult, FlowTimers } from "./flow-types.js";
|
|
4
4
|
/** Ключ данных чата (`Chat.data`) с пометкой тестового чата и сдвигом его часов. */
|
|
5
5
|
export declare const FLOW_TEST_CHAT_KEY = "flowTestChat";
|
|
6
6
|
/** Данные нового тестового чата: `service.createChat(userId, { data: flowTestChatData() })`. */
|
|
@@ -9,6 +9,11 @@ export declare function flowTestChatData(): Record<string, unknown>;
|
|
|
9
9
|
export declare function testChatOffset(data: Record<string, unknown> | undefined): number | null;
|
|
10
10
|
/** Часы чата, мс: часы сервера плюс сдвиг тестового чата. */
|
|
11
11
|
export declare const chatTime: (run: Pick<FlowRun, "timeOffset"> | null | undefined, serverNow: number) => number;
|
|
12
|
+
/** Перемотка не удалась по понятной причине: `nothing_scheduled` — впереди сценарий сам ничего не напишет. */
|
|
13
|
+
export declare class FlowTestClockError extends Error {
|
|
14
|
+
readonly code: "nothing_scheduled" | "not_reached";
|
|
15
|
+
constructor(code: "nothing_scheduled" | "not_reached", message: string);
|
|
16
|
+
}
|
|
12
17
|
/**
|
|
13
18
|
* Промотать часы тестового чата вперёд: на `ms` или до начала следующего дня сценария (`to: "next_day_start"`). Сдвиг
|
|
14
19
|
* в данные чата, ближайший таймер пересчитан по новым часам и, если наступил, срабатывает сразу (агент пишет первым по
|
|
@@ -21,6 +26,4 @@ export declare function advanceTestClock(input: {
|
|
|
21
26
|
ownerId: string;
|
|
22
27
|
chatId: string;
|
|
23
28
|
clock: FlowTestClockInput;
|
|
24
|
-
}): Promise<
|
|
25
|
-
timeOffset: number;
|
|
26
|
-
}>;
|
|
29
|
+
}): Promise<FlowTestClockResult>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ykurilov/agent-chat-flows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -56,13 +56,13 @@
|
|
|
56
56
|
"ai": "7.0.122",
|
|
57
57
|
"drizzle-orm": "^0.45.3",
|
|
58
58
|
"zod": "^4.6.5",
|
|
59
|
-
"@ykurilov/agent-chat-core": "0.
|
|
59
|
+
"@ykurilov/agent-chat-core": "0.65.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"better-sqlite3": "^13.0.3",
|
|
63
63
|
"pg": "^8.23.0",
|
|
64
64
|
"react": "^19.3.0",
|
|
65
|
-
"@ykurilov/agent-chat-react": "^0.
|
|
65
|
+
"@ykurilov/agent-chat-react": "^0.65.0"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"@ykurilov/agent-chat-react": {
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"pg": "^8.23.0",
|
|
91
91
|
"react": "^19.3.0",
|
|
92
92
|
"tsx": "4.23.15",
|
|
93
|
-
"@ykurilov/agent-chat-react": "0.
|
|
93
|
+
"@ykurilov/agent-chat-react": "0.65.0"
|
|
94
94
|
},
|
|
95
95
|
"scripts": {
|
|
96
96
|
"build": "node ../../scripts/build-package.mjs",
|
package/dist/chunk-H3AN5YUN.js
DELETED
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_FIRST_MESSAGES,
|
|
3
|
-
DEFAULT_SIDE_TASK_MAX_ATTEMPTS,
|
|
4
|
-
agentFlowSchema,
|
|
5
|
-
appendPath,
|
|
6
|
-
clockMinutes,
|
|
7
|
-
dayInProgress,
|
|
8
|
-
fieldById,
|
|
9
|
-
fieldValueFits,
|
|
10
|
-
isFinal,
|
|
11
|
-
nextClockTime,
|
|
12
|
-
parseClock,
|
|
13
|
-
startOfNextDay,
|
|
14
|
-
stateById
|
|
15
|
-
} from "./chunk-LT4QELAZ.js";
|
|
16
|
-
|
|
17
|
-
// src/format/flow-json-schema.ts
|
|
18
|
-
import { z } from "zod";
|
|
19
|
-
function flowJsonSchema() {
|
|
20
|
-
return {
|
|
21
|
-
...z.toJSONSchema(agentFlowSchema, { io: "input", unrepresentable: "any" }),
|
|
22
|
-
$id: "https://github.com/ykurilov/agent-chat-sdk/packages/flows/agent-flow.schema.json",
|
|
23
|
-
title: "\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u043F\u043E\u0432\u0435\u0434\u0435\u043D\u0438\u044F \u0430\u0433\u0435\u043D\u0442\u0430 (agent-flow)"
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/engine/first-messages.ts
|
|
28
|
-
function resolveFirstMessages(setting) {
|
|
29
|
-
if (setting === false) return null;
|
|
30
|
-
const rules = {
|
|
31
|
-
quietHours: { ...DEFAULT_FIRST_MESSAGES.quietHours, ...setting?.quietHours },
|
|
32
|
-
maxPerDay: setting?.maxPerDay ?? DEFAULT_FIRST_MESSAGES.maxPerDay,
|
|
33
|
-
...setting?.canWriteNow ? { canWriteNow: setting.canWriteNow } : {}
|
|
34
|
-
};
|
|
35
|
-
for (const clock of [rules.quietHours.from, rules.quietHours.to]) {
|
|
36
|
-
if (parseClock(clock) === null) throw new Error(`\u0422\u0438\u0445\u0438\u0435 \u0447\u0430\u0441\u044B \u043F\u0438\u0448\u0443\u0442\u0441\u044F \u043A\u0430\u043A \xAB\u0427\u0427:\u041C\u041C\xBB, \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \xAB21:00\xBB, \u0430 \u043D\u0435 \xAB${clock}\xBB.`);
|
|
37
|
-
}
|
|
38
|
-
return rules;
|
|
39
|
-
}
|
|
40
|
-
function isQuiet(quietHours, at, timeZone) {
|
|
41
|
-
const from = parseClock(quietHours.from);
|
|
42
|
-
const to = parseClock(quietHours.to);
|
|
43
|
-
const now = clockMinutes(at, timeZone);
|
|
44
|
-
if (from === to) return false;
|
|
45
|
-
return from < to ? now >= from && now < to : now >= from || now < to;
|
|
46
|
-
}
|
|
47
|
-
function firstAwake(quietHours, at, timeZone) {
|
|
48
|
-
return isQuiet(quietHours, at, timeZone) ? nextClockTime(at, parseClock(quietHours.to), timeZone) : at;
|
|
49
|
-
}
|
|
50
|
-
function firstMessageSlot(rules, input) {
|
|
51
|
-
const { at, timeZone } = input;
|
|
52
|
-
if (input.sentToday >= rules.maxPerDay) {
|
|
53
|
-
return { allowed: false, retryAt: firstAwake(rules.quietHours, startOfNextDay(at, timeZone), timeZone) };
|
|
54
|
-
}
|
|
55
|
-
const awake = firstAwake(rules.quietHours, at, timeZone);
|
|
56
|
-
return awake === at ? { allowed: true } : { allowed: false, retryAt: awake };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// src/engine/migrate-run.ts
|
|
60
|
-
function migrateRun(run, next, map, now) {
|
|
61
|
-
const exists = (id) => stateById(next, id) !== void 0;
|
|
62
|
-
const renamed = (id) => !exists(id) && map[id] !== void 0 && exists(map[id]) ? map[id] : id;
|
|
63
|
-
const noteIds = new Set(next.chatNotes.map((field) => field.id));
|
|
64
|
-
const notes = Object.fromEntries(Object.entries(run.notes).filter(([id]) => noteIds.has(id)));
|
|
65
|
-
const path = run.path.map((entry) => ({ ...entry, state: renamed(entry.state), from: entry.from === null ? null : renamed(entry.from) }));
|
|
66
|
-
const pausedClocks = Object.fromEntries(
|
|
67
|
-
Object.entries(run.pausedClocks).map(([id, ms]) => [renamed(id), ms]).filter(([id]) => exists(id))
|
|
68
|
-
);
|
|
69
|
-
if (exists(run.state)) return { ...run, path, pausedClocks, notes };
|
|
70
|
-
const targetId = map[run.state];
|
|
71
|
-
if (targetId === void 0) {
|
|
72
|
-
throw new Error(`\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \xAB${run.state}\xBB \u043D\u0435\u0442 \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \xAB${next.id}\xBB, \u0430 \u043A\u0443\u0434\u0430 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442 \u2014 \u043D\u0435 \u0432\u044B\u0431\u0440\u0430\u043D\u043E.`);
|
|
73
|
-
}
|
|
74
|
-
const target = stateById(next, targetId);
|
|
75
|
-
if (!target) throw new Error(`\u0421\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \xAB${targetId}\xBB \u043D\u0435\u0442 \u0432 \u043D\u043E\u0432\u043E\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \xAB${next.id}\xBB: \u0442\u0443\u0434\u0430 \u043D\u0435 \u043F\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0447\u0430\u0442 \u0438\u0437 \xAB${run.state}\xBB.`);
|
|
76
|
-
return {
|
|
77
|
-
...run,
|
|
78
|
-
state: target.id,
|
|
79
|
-
enteredAt: now,
|
|
80
|
-
pausedClocks: {},
|
|
81
|
-
notes,
|
|
82
|
-
path: appendPath(path, { state: target.id, at: now, tags: target.tags, from: run.state, exitId: null, decidedBy: "migrate" }),
|
|
83
|
-
lastRejected: null
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/engine/day-frame.ts
|
|
88
|
-
var 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!";
|
|
89
|
-
function frameState(humanMessagesToday2, frame) {
|
|
90
|
-
if (!frame) return "normal";
|
|
91
|
-
const { softFrom, hardAt } = frame;
|
|
92
|
-
if (hardAt !== void 0 && humanMessagesToday2 > hardAt) return "closed";
|
|
93
|
-
if (hardAt !== void 0 && humanMessagesToday2 === hardAt) return "last";
|
|
94
|
-
if (softFrom !== void 0 && humanMessagesToday2 >= softFrom) return "soft";
|
|
95
|
-
return "normal";
|
|
96
|
-
}
|
|
97
|
-
function hardFrameText(frame) {
|
|
98
|
-
return frame?.hardText?.trim() || DEFAULT_HARD_FRAME_TEXT;
|
|
99
|
-
}
|
|
100
|
-
function humanMessagesToday(run, at, timeZone, schedule) {
|
|
101
|
-
return run.dayCounts?.[dayInProgress(at, timeZone, schedule)] ?? 0;
|
|
102
|
-
}
|
|
103
|
-
function dayClosedToday(dayClosedOn, at, timeZone, schedule) {
|
|
104
|
-
return dayClosedOn !== null && dayClosedOn === dayInProgress(at, timeZone, schedule);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// src/engine/side-tasks.ts
|
|
108
|
-
var TIME_ZONE_TASK_ID = "ask_time_zone";
|
|
109
|
-
var TIME_ZONE_TASK = {
|
|
110
|
-
id: TIME_ZONE_TASK_ID,
|
|
111
|
-
text: "\u0423\u0437\u043D\u0430\u0439, \u0438\u0437 \u043A\u0430\u043A\u043E\u0433\u043E \u0447\u0435\u043B\u043E\u0432\u0435\u043A \u0433\u043E\u0440\u043E\u0434\u0430, \u0447\u0442\u043E\u0431\u044B \u043F\u0438\u0441\u0430\u0442\u044C \u0435\u043C\u0443 \u0432 \u0443\u0434\u043E\u0431\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F. \u0423\u0437\u043D\u0430\u043B \u0433\u043E\u0440\u043E\u0434 \u2014 \u0437\u0430\u043F\u0438\u0448\u0438 \u0435\u0433\u043E \u043F\u043E\u044F\u0441 \u0447\u0435\u0440\u0435\u0437 \xABflow_time_zone\xBB \u0438 \u043E\u0442\u043C\u0435\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u0440\u0435\u0448\u0451\u043D\u043D\u043E\u0439.",
|
|
112
|
-
when: "\u0432 \u043F\u0435\u0440\u0432\u044B\u0439 \u0434\u0435\u043D\u044C, \u0434\u043E \u043D\u043E\u0447\u0438; \u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440: \xAB\u041A\u0441\u0442\u0430\u0442\u0438, \u0432\u044B \u043E\u0442\u043A\u0443\u0434\u0430? \u0425\u043E\u0447\u0443 \u043F\u0438\u0441\u0430\u0442\u044C \u0432\u0430\u043C \u0432 \u0443\u0434\u043E\u0431\u043D\u043E\u0435 \u0432\u0440\u0435\u043C\u044F\xBB",
|
|
113
|
-
maxAttempts: DEFAULT_SIDE_TASK_MAX_ATTEMPTS,
|
|
114
|
-
perConversation: false
|
|
115
|
-
};
|
|
116
|
-
function sideTaskById(flow, id) {
|
|
117
|
-
if (id === TIME_ZONE_TASK_ID && flow.schedule) return TIME_ZONE_TASK;
|
|
118
|
-
return flow.sideTasks?.find((task) => task.id === id);
|
|
119
|
-
}
|
|
120
|
-
var hasSideTasks = (flow) => flow.schedule !== void 0 || (flow.sideTasks?.length ?? 0) > 0;
|
|
121
|
-
function sideTaskRecord(task, chatId, attempts) {
|
|
122
|
-
const owner = task.perConversation ? chatId : null;
|
|
123
|
-
return attempts.find((record) => record.taskId === task.id && record.chatId === owner);
|
|
124
|
-
}
|
|
125
|
-
function sideTaskFieldKnown(task, context) {
|
|
126
|
-
if (task.field === void 0) return false;
|
|
127
|
-
const found = fieldById(context.flow, task.field);
|
|
128
|
-
if (!found) return false;
|
|
129
|
-
return fieldValueFits(found.field, found.place === "notes" ? context.run.notes[task.field] : context.card[task.field]);
|
|
130
|
-
}
|
|
131
|
-
function sideTaskStanding(task, context) {
|
|
132
|
-
const record = sideTaskRecord(task, context.chatId, context.attempts);
|
|
133
|
-
if (record?.doneAt || sideTaskFieldKnown(task, context)) return "done";
|
|
134
|
-
return (record?.asks ?? 0) >= task.maxAttempts ? "spent" : "open";
|
|
135
|
-
}
|
|
136
|
-
function sideTaskPaceReached(flow, run) {
|
|
137
|
-
return run.sideQuestionAt === null || run.humanMessages - run.sideQuestionAt >= flow.pace.sideQuestionEveryMessages;
|
|
138
|
-
}
|
|
139
|
-
function openSideTasks(input) {
|
|
140
|
-
const { flow, run } = input;
|
|
141
|
-
const state = stateById(flow, run.state);
|
|
142
|
-
if (!state || isFinal(state) || state.noSideQuestions) return [];
|
|
143
|
-
const askCity = input.askTimeZone === true && flow.schedule !== void 0 && sideTaskStanding(TIME_ZONE_TASK, input) === "open";
|
|
144
|
-
const own = sideTaskPaceReached(flow, run) ? (flow.sideTasks ?? []).filter((task) => sideTaskStanding(task, input) === "open") : [];
|
|
145
|
-
return askCity ? [TIME_ZONE_TASK, ...own] : own;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export {
|
|
149
|
-
flowJsonSchema,
|
|
150
|
-
resolveFirstMessages,
|
|
151
|
-
firstMessageSlot,
|
|
152
|
-
migrateRun,
|
|
153
|
-
frameState,
|
|
154
|
-
hardFrameText,
|
|
155
|
-
humanMessagesToday,
|
|
156
|
-
dayClosedToday,
|
|
157
|
-
TIME_ZONE_TASK_ID,
|
|
158
|
-
sideTaskById,
|
|
159
|
-
hasSideTasks,
|
|
160
|
-
sideTaskRecord,
|
|
161
|
-
sideTaskFieldKnown,
|
|
162
|
-
sideTaskStanding,
|
|
163
|
-
openSideTasks
|
|
164
|
-
};
|
|
165
|
-
//# sourceMappingURL=chunk-H3AN5YUN.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/format/flow-json-schema.ts","../src/engine/first-messages.ts","../src/engine/migrate-run.ts","../src/engine/day-frame.ts","../src/engine/side-tasks.ts"],"sourcesContent":["import { z } from \"zod\";\nimport { agentFlowSchema } from \"./flow-schema\";\n\n/** JSON Schema сценария (то, что пишет автор: значения по умолчанию необязательны). Лежит в пакете: `agent-flow.schema.json`. */\nexport function flowJsonSchema(): Record<string, unknown> {\n return {\n ...z.toJSONSchema(agentFlowSchema, { io: \"input\", unrepresentable: \"any\" }),\n $id: \"https://github.com/ykurilov/agent-chat-sdk/packages/flows/agent-flow.schema.json\",\n title: \"Сценарий поведения агента (agent-flow)\",\n };\n}\n","import { DEFAULT_FIRST_MESSAGES, type FirstMessagesRules, type FirstMessagesSetting } from \"./flow-run\";\nimport { clockMinutes, nextClockTime, parseClock, startOfNextDay } from \"./zoned-time\";\n\nexport { localDay } from \"./zoned-time\";\n\nexport interface FirstMessageInput {\n /** Когда агент хочет написать, мс. */\n at: number;\n timeZone: string;\n /** Сколько уже написал первым за эти сутки человека (`localDay`). */\n sentToday: number;\n}\n\n/** Настройка приложения поверх значений SDK (→ В10). `false` — правила выключены: `null`, агент пишет первым без ограничений. */\nexport function resolveFirstMessages(setting: FirstMessagesSetting | undefined): FirstMessagesRules | null {\n if (setting === false) return null;\n const rules: FirstMessagesRules = {\n quietHours: { ...DEFAULT_FIRST_MESSAGES.quietHours, ...setting?.quietHours },\n maxPerDay: setting?.maxPerDay ?? DEFAULT_FIRST_MESSAGES.maxPerDay,\n ...(setting?.canWriteNow ? { canWriteNow: setting.canWriteNow } : {}),\n };\n for (const clock of [rules.quietHours.from, rules.quietHours.to]) {\n if (parseClock(clock) === null) throw new Error(`Тихие часы пишутся как «ЧЧ:ММ», например «21:00», а не «${clock}».`);\n }\n return rules;\n}\n\ntype QuietHours = FirstMessagesRules[\"quietHours\"];\n\n/** Тихо ли сейчас на часах человека. Тихие часы могут идти через полночь; `from` = `to` — тихих часов нет. */\nfunction isQuiet(quietHours: QuietHours, at: number, timeZone: string): boolean {\n const from = parseClock(quietHours.from)!;\n const to = parseClock(quietHours.to)!;\n const now = clockMinutes(at, timeZone);\n if (from === to) return false;\n return from < to ? now >= from && now < to : now >= from || now < to;\n}\n\n/** Первый момент от `at`, когда не тихо. */\nfunction firstAwake(quietHours: QuietHours, at: number, timeZone: string): number {\n return isQuiet(quietHours, at, timeZone) ? nextClockTime(at, parseClock(quietHours.to)!, timeZone) : at;\n}\n\n/**\n * Можно ли написать первым сейчас; нет — на когда перенести. Тихие часы → их конец. Потолок за сутки исчерпан →\n * следующие сутки человека, после тихих часов.\n */\nexport function firstMessageSlot(\n rules: Omit<FirstMessagesRules, \"canWriteNow\">,\n input: FirstMessageInput,\n): { allowed: true } | { allowed: false; retryAt: number } {\n const { at, timeZone } = input;\n if (input.sentToday >= rules.maxPerDay) {\n return { allowed: false, retryAt: firstAwake(rules.quietHours, startOfNextDay(at, timeZone), timeZone) };\n }\n const awake = firstAwake(rules.quietHours, at, timeZone);\n return awake === at ? { allowed: true } : { allowed: false, retryAt: awake };\n}\n","import type { AgentFlow } from \"../format/flow-format\";\nimport type { FlowRun } from \"./flow-run\";\nimport { stateById } from \"./flow-graph\";\nimport { appendPath } from \"./run-updates\";\n\n/** Куда перевести чаты из состояний, которых нет в новой версии: id старого → id нового. */\nexport type StateMap = Record<string, string>;\n\n/**\n * Переводит чат на новую версию сценария (→ В11). Состояние есть в новой версии — часы и путь целы. Удалено — вход в\n * состояние из `map`; без цели — ошибка (её ловит перевод чатов раньше). Заметки чата — по совпадающим `id`.\n * Удалённые состояния в пути и в остановленных часах переименовываются по `map`: так «назад» ведёт туда, куда автор\n * отправил удалённое состояние.\n */\nexport function migrateRun(run: FlowRun, next: AgentFlow, map: StateMap, now: number): FlowRun {\n const exists = (id: string) => stateById(next, id) !== undefined;\n const renamed = (id: string) => (!exists(id) && map[id] !== undefined && exists(map[id]) ? map[id] : id);\n const noteIds = new Set(next.chatNotes.map((field) => field.id));\n const notes = Object.fromEntries(Object.entries(run.notes).filter(([id]) => noteIds.has(id)));\n const path = run.path.map((entry) => ({ ...entry, state: renamed(entry.state), from: entry.from === null ? null : renamed(entry.from) }));\n const pausedClocks = Object.fromEntries(\n Object.entries(run.pausedClocks)\n .map(([id, ms]) => [renamed(id), ms] as const)\n .filter(([id]) => exists(id)),\n );\n if (exists(run.state)) return { ...run, path, pausedClocks, notes };\n\n const targetId = map[run.state];\n if (targetId === undefined) {\n throw new Error(`Состояния «${run.state}» нет в новой версии сценария «${next.id}», а куда перевести чат — не выбрано.`);\n }\n const target = stateById(next, targetId);\n if (!target) throw new Error(`Состояния «${targetId}» нет в новой версии сценария «${next.id}»: туда не перевести чат из «${run.state}».`);\n return {\n ...run,\n state: target.id,\n enteredAt: now,\n pausedClocks: {},\n notes,\n path: appendPath(path, { state: target.id, at: now, tags: target.tags, from: run.state, exitId: null, decidedBy: \"migrate\" }),\n lastRejected: null,\n };\n}\n","import type { FlowSchedule } from \"../format/flow-format\";\nimport type { FlowRun } from \"./flow-run\";\nimport { dayInProgress } from \"./flow-days\";\n\n// Рамки дня по числу сообщений человека за день сценария, по plan.md, «Рамки дня и решение о входящем». Чистые функции.\n\n/** `normal` — как обычно; `soft` — сворачиваться (строка хвоста); `last` — готовая фраза `hardText`; `closed` — молчать до утра. */\nexport type FrameState = \"normal\" | \"soft\" | \"last\" | \"closed\";\n\n/** Готовая фраза жёсткой рамки, если автор свою не задал. */\nexport const DEFAULT_HARD_FRAME_TEXT = \"На сегодня всё — утром обязательно отвечу!\";\n\n/**\n * Состояние рамки дня для `humanMessagesToday`-го сообщения человека за день сценария: на `hardAt`-м — готовая фраза,\n * после — молчание; с `softFrom`-го — сворачиваться. Рамок нет — `normal`.\n */\nexport function frameState(humanMessagesToday: number, frame: FlowSchedule[\"dayFrame\"]): FrameState {\n if (!frame) return \"normal\";\n const { softFrom, hardAt } = frame;\n if (hardAt !== undefined && humanMessagesToday > hardAt) return \"closed\";\n if (hardAt !== undefined && humanMessagesToday === hardAt) return \"last\";\n if (softFrom !== undefined && humanMessagesToday >= softFrom) return \"soft\";\n return \"normal\";\n}\n\n/** Готовая фраза жёсткой рамки: автора, иначе по умолчанию. */\nexport function hardFrameText(frame: FlowSchedule[\"dayFrame\"]): string {\n return frame?.hardText?.trim() || DEFAULT_HARD_FRAME_TEXT;\n}\n\n/** Сколько сообщений человека уже посчитано за день сценария, который идёт в `at` (`run.dayCounts`). */\nexport function humanMessagesToday(run: FlowRun, at: number, timeZone: string, schedule: FlowSchedule): number {\n return run.dayCounts?.[dayInProgress(at, timeZone, schedule)] ?? 0;\n}\n\n/** Человек закрыл день (`flow_close_day`, запись `dayClosedOn`), и этот день ещё идёт: сегодня первым не пишем. */\nexport function dayClosedToday(dayClosedOn: string | null, at: number, timeZone: string, schedule: FlowSchedule): boolean {\n return dayClosedOn !== null && dayClosedOn === dayInProgress(at, timeZone, schedule);\n}\n","import { DEFAULT_SIDE_TASK_MAX_ATTEMPTS, type AgentFlow, type FlowSideTask } from \"../format/flow-format\";\nimport type { FlowRun } from \"./flow-run\";\nimport { fieldValueFits } from \"./data-conditions\";\nimport { fieldById, isFinal, stateById } from \"./flow-graph\";\n\n// Попутные задачи: дела между делом («предложить подписаться на канал»). Темп общий с вопросами о человеке\n// (`pace.sideQuestionEveryMessages`), попыток у задачи не больше `maxAttempts`, решённая не всплывает.\n\n/** Попытки задачи, как их отдаёт хранилище: на человека (`chatId: null`) или на разговор. */\nexport interface SideTaskAttempts {\n taskId: string;\n chatId: string | null;\n asks: number;\n doneAt: string | null;\n}\n\n/** Встроенная задача: пока пояс человека неизвестен, в первый день до ночи узнать город (В1). Первая по важности. */\nexport const TIME_ZONE_TASK_ID = \"ask_time_zone\";\nexport const TIME_ZONE_TASK: FlowSideTask = {\n id: TIME_ZONE_TASK_ID,\n text: \"Узнай, из какого человек города, чтобы писать ему в удобное время. Узнал город — запиши его пояс через «flow_time_zone» и отметь задачу решённой.\",\n when: \"в первый день, до ночи; например: «Кстати, вы откуда? Хочу писать вам в удобное время»\",\n maxAttempts: DEFAULT_SIDE_TASK_MAX_ATTEMPTS,\n perConversation: false,\n};\n\n/** Задача по id: встроенная «узнать город» — только у сценария с «Днями и временем», иначе задача сценария. */\nexport function sideTaskById(flow: AgentFlow, id: string): FlowSideTask | undefined {\n if (id === TIME_ZONE_TASK_ID && flow.schedule) return TIME_ZONE_TASK;\n return flow.sideTasks?.find((task) => task.id === id);\n}\n\n/** Есть ли у сценария попутные задачи: свои или встроенная. Нет — ни инструмента, ни правила, ни строки (как в 0.48). */\nexport const hasSideTasks = (flow: AgentFlow) => flow.schedule !== undefined || (flow.sideTasks?.length ?? 0) > 0;\n\nexport interface SideTaskContext {\n flow: AgentFlow;\n run: FlowRun;\n /** Разговор, в котором идёт ход: по нему считаются попытки задач «заново в каждом разговоре». */\n chatId: string;\n attempts: readonly SideTaskAttempts[];\n /** Значения карточки клиента по `id` поля. Заметки чата берутся из `run.notes`. */\n card: Readonly<Record<string, unknown>>;\n /** Пояс человека неизвестен, а сегодня его первый день до первой ночи: тогда открыта встроенная задача «узнать город». Отдаёт этап 4. */\n askTimeZone?: boolean;\n}\n\nexport type OpenSideTasksInput = SideTaskContext;\n\n/** Решена ли задача: отмечена или её поле уже известно. Потрачена ли: попытки кончились. Иначе — открыта. */\nexport type SideTaskStanding = \"open\" | \"done\" | \"spent\";\n\n/** Попытки задачи: на человека — запись без разговора, у «заново в каждом разговоре» — запись этого разговора. */\nexport function sideTaskRecord(task: FlowSideTask, chatId: string, attempts: readonly SideTaskAttempts[]): SideTaskAttempts | undefined {\n const owner = task.perConversation ? chatId : null;\n return attempts.find((record) => record.taskId === task.id && record.chatId === owner);\n}\n\n/** Известно ли поле задачи: значение подходит типу поля. Поля нет в сценарии — не известно. */\nexport function sideTaskFieldKnown(task: FlowSideTask, context: Pick<SideTaskContext, \"flow\" | \"run\" | \"card\">): boolean {\n if (task.field === undefined) return false;\n const found = fieldById(context.flow, task.field);\n if (!found) return false;\n return fieldValueFits(found.field, found.place === \"notes\" ? context.run.notes[task.field] : context.card[task.field]);\n}\n\n/** Где задача сейчас — без учёта темпа и места в сценарии. */\nexport function sideTaskStanding(task: FlowSideTask, context: SideTaskContext): SideTaskStanding {\n const record = sideTaskRecord(task, context.chatId, context.attempts);\n if (record?.doneAt || sideTaskFieldKnown(task, context)) return \"done\";\n return (record?.asks ?? 0) >= task.maxAttempts ? \"spent\" : \"open\";\n}\n\n/** Прошло ли с прошлого вопроса или задачи между делом столько сообщений человека, сколько велит `pace`. */\nexport function sideTaskPaceReached(flow: AgentFlow, run: FlowRun): boolean {\n return run.sideQuestionAt === null || run.humanMessages - run.sideQuestionAt >= flow.pace.sideQuestionEveryMessages;\n}\n\n/**\n * Задачи, которые агент может предложить сейчас: открытые, и только если темп позволяет, состояние не «без вопросов» и\n * сценарий не кончился. Порядок — как в сценарии. Встроенная «узнать город» — первой и вне общего темпа: у неё только\n * первый день до ночи. Задач нет — пусто, как в 0.48.\n */\nexport function openSideTasks(input: OpenSideTasksInput): FlowSideTask[] {\n const { flow, run } = input;\n const state = stateById(flow, run.state);\n if (!state || isFinal(state) || state.noSideQuestions) return [];\n const askCity = input.askTimeZone === true && flow.schedule !== undefined && sideTaskStanding(TIME_ZONE_TASK, input) === \"open\";\n const own = sideTaskPaceReached(flow, run) ? (flow.sideTasks ?? []).filter((task) => sideTaskStanding(task, input) === \"open\") : [];\n return askCity ? [TIME_ZONE_TASK, ...own] : own;\n}\n"],"mappings":"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","names":["humanMessagesToday"]}
|