@ykurilov/agent-chat-flows 0.80.1 → 0.82.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 +93 -0
- package/dist/{chunk-CZ3EDVYW.js → chunk-7ES2PXNH.js} +2 -2
- package/dist/{chunk-CZ3EDVYW.js.map → chunk-7ES2PXNH.js.map} +1 -1
- package/dist/{chunk-DCTRMCGV.js → chunk-DOKHNTPU.js} +20 -2
- package/dist/chunk-DOKHNTPU.js.map +1 -0
- package/dist/index.js +381 -188
- package/dist/index.js.map +1 -1
- package/dist/postgres.js +56 -7
- package/dist/postgres.js.map +1 -1
- package/dist/react.js +1 -1
- package/dist/sqlite.js +51 -7
- package/dist/sqlite.js.map +1 -1
- package/dist/types/runtime/flow-delivery.d.ts +22 -13
- package/dist/types/runtime/flow-lease.d.ts +19 -0
- package/dist/types/runtime/flow-timers.d.ts +2 -2
- package/dist/types/runtime/flow-types.d.ts +30 -3
- package/dist/types/store/flow-store-shared.d.ts +5 -1
- package/dist/types/store/flow-store.d.ts +56 -2
- package/dist/types/store/memory.d.ts +1 -1
- package/dist/types/store/postgres.d.ts +1 -1
- package/dist/types/store/sqlite.d.ts +1 -1
- package/package.json +4 -4
- package/dist/chunk-DCTRMCGV.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
transitionReport,
|
|
58
58
|
withPending,
|
|
59
59
|
withReopen
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-7ES2PXNH.js";
|
|
61
61
|
import {
|
|
62
62
|
AGENT_FLOW_FORMAT,
|
|
63
63
|
AGENT_FLOW_FORMAT_VERSION,
|
|
@@ -143,14 +143,17 @@ import {
|
|
|
143
143
|
withoutUndeliveredTouch
|
|
144
144
|
} from "./chunk-RMWVLMGY.js";
|
|
145
145
|
import {
|
|
146
|
+
FLOW_DELIVERY_EXIT,
|
|
146
147
|
appliedInTime,
|
|
147
148
|
byVersionAndState,
|
|
148
149
|
compareText,
|
|
149
150
|
mappedState,
|
|
150
151
|
migratePending,
|
|
151
152
|
normalizeTime,
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
normalizedDelivery,
|
|
154
|
+
pendingAdded,
|
|
155
|
+
sameDelivery
|
|
156
|
+
} from "./chunk-DOKHNTPU.js";
|
|
154
157
|
|
|
155
158
|
// src/runtime/flow-service-blocks.ts
|
|
156
159
|
var FLOW_SERVICE_BLOCKS = [
|
|
@@ -333,7 +336,7 @@ async function previewConfirmedMigration(input, deps) {
|
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
// src/runtime/agent-flow.ts
|
|
336
|
-
import { createHash as createHash2, randomUUID as
|
|
339
|
+
import { createHash as createHash2, randomUUID as randomUUID5 } from "crypto";
|
|
337
340
|
import {
|
|
338
341
|
ChatNotFoundError as ChatNotFoundError4,
|
|
339
342
|
flowStateCommitter as flowStateCommitter2
|
|
@@ -969,122 +972,34 @@ function errorText2(error) {
|
|
|
969
972
|
|
|
970
973
|
// src/runtime/flow-timers.ts
|
|
971
974
|
import { randomUUID } from "crypto";
|
|
972
|
-
import { ChatNotFoundError
|
|
975
|
+
import { ChatNotFoundError, SpendingLimitReachedError } from "@ykurilov/agent-chat-core";
|
|
973
976
|
|
|
974
|
-
// src/runtime/flow-
|
|
975
|
-
import { answerText, ChatNotFoundError, deliverableParts } from "@ykurilov/agent-chat-core";
|
|
976
|
-
var FLOW_DELIVERY_EXIT = "@delivery";
|
|
977
|
-
var RETRY_DELAYS_MS = [6e4, 5 * 6e4, 30 * 6e4];
|
|
977
|
+
// src/runtime/flow-lease.ts
|
|
978
978
|
var errorText3 = (error) => error instanceof Error ? error.message : String(error);
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
const
|
|
984
|
-
|
|
985
|
-
if (!
|
|
986
|
-
return
|
|
979
|
+
function holdLease(input) {
|
|
980
|
+
const { store, chatId, owner, leaseMs } = input;
|
|
981
|
+
let lost = false;
|
|
982
|
+
let renewing = null;
|
|
983
|
+
const renewNow = async () => {
|
|
984
|
+
if (lost) return false;
|
|
985
|
+
if (!await store.renewLease({ chatId, owner, now: input.now().toISOString(), leaseMs })) lost = true;
|
|
986
|
+
return !lost;
|
|
987
987
|
};
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const pinned = await runtime.chatFlow(ownerId, chatId);
|
|
999
|
-
if (!pinned || !input.finish) {
|
|
1000
|
-
await store.endChat(chatId, { reason: "blocked", at: isoOf(runtime.now()) });
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
const { values } = await (await portOf({ ownerId, chatId })).getChatState(ownerId, chatId);
|
|
1004
|
-
const at = runtime.now();
|
|
1005
|
-
await input.finish().end({ ownerId, chatId, pinned, run: await runOutsideTurn(runtime, { ownerId, chatId }, pinned.flow, values, at), reason: "blocked", at });
|
|
1006
|
-
}
|
|
1007
|
-
async function settle(message, outcome, counted) {
|
|
1008
|
-
const { ownerId, chatId, messageId } = message;
|
|
1009
|
-
switch (outcome.status) {
|
|
1010
|
-
case "delivered":
|
|
1011
|
-
await store.setDelivery(chatId, null);
|
|
1012
|
-
await input.firstMessages?.()?.record({ ownerId, ...counted });
|
|
1013
|
-
return outcome.status;
|
|
1014
|
-
case "retry": {
|
|
1015
|
-
if (message.attempt > RETRY_DELAYS_MS.length) return settle(message, { status: "failed" }, counted);
|
|
1016
|
-
const delay = outcome.afterMs ?? RETRY_DELAYS_MS[message.attempt - 1];
|
|
1017
|
-
const retryAt = runtime.now() + delay;
|
|
1018
|
-
const reasons = message.reasons.map((reason) => JSON.stringify(reason));
|
|
1019
|
-
await store.setDelivery(chatId, { messageId, attempt: message.attempt + 1, retryAt: isoOf(retryAt), reasons });
|
|
1020
|
-
const record = await store.getChat(chatId);
|
|
1021
|
-
await store.setDue(chatId, { at: isoOf(retryAt), exitId: FLOW_DELIVERY_EXIT, branchHead: record?.dueBranchHead ?? null });
|
|
1022
|
-
return outcome.status;
|
|
1023
|
-
}
|
|
1024
|
-
case "failed":
|
|
1025
|
-
await store.setDelivery(chatId, null);
|
|
1026
|
-
await markUndelivered(ownerId, chatId, messageId);
|
|
1027
|
-
return outcome.status;
|
|
1028
|
-
case "window_closed":
|
|
1029
|
-
await store.setDelivery(chatId, null);
|
|
1030
|
-
await markUndelivered(ownerId, chatId, messageId);
|
|
1031
|
-
await store.setWindowClosed(chatId, true);
|
|
1032
|
-
return outcome.status;
|
|
1033
|
-
case "blocked":
|
|
1034
|
-
await markUndelivered(ownerId, chatId, messageId);
|
|
1035
|
-
await blocked(ownerId, chatId);
|
|
1036
|
-
return outcome.status;
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
async function send(message) {
|
|
1040
|
-
const deliver = input.deliver();
|
|
1041
|
-
if (!deliver) throw new Error("\u0423 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 \u0441\u0432\u043E\u0435\u0439 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438 (attach({ deliver }))");
|
|
1042
|
-
try {
|
|
1043
|
-
return await deliver(message);
|
|
1044
|
-
} catch (error) {
|
|
1045
|
-
runtime.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${message.chatId} \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430 \u0443\u043F\u0430\u043B\u0430: ${errorText3(error)}`);
|
|
1046
|
-
return { status: "retry" };
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
async function sendAndSettle(message, counted) {
|
|
1050
|
-
return settle(message, await send(message), counted);
|
|
1051
|
-
}
|
|
988
|
+
const heartbeat = setInterval(() => {
|
|
989
|
+
renewing ??= renewNow().then(
|
|
990
|
+
() => {
|
|
991
|
+
},
|
|
992
|
+
(error) => input.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043F\u0440\u043E\u0434\u043B\u0435\u043D\u0430: ${errorText3(error)}`)
|
|
993
|
+
).finally(() => {
|
|
994
|
+
renewing = null;
|
|
995
|
+
});
|
|
996
|
+
}, leaseMs / 3);
|
|
997
|
+
heartbeat.unref?.();
|
|
1052
998
|
return {
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
await input.firstMessages?.()?.record({ ownerId, at, timeZone });
|
|
1058
|
-
return null;
|
|
1059
|
-
}
|
|
1060
|
-
if (!answer || answer.status !== "complete" || !hasContent(answer)) return null;
|
|
1061
|
-
return sendAndSettle({ ownerId, chatId, messageId: answer.messageId, text: answer.text, parts: answer.parts, reasons, attempt: 1 }, { at, timeZone });
|
|
1062
|
-
},
|
|
1063
|
-
async retry(record, serverAt) {
|
|
1064
|
-
const pending = record.delivery;
|
|
1065
|
-
if (!pending) return null;
|
|
1066
|
-
const { ownerId, chatId } = record;
|
|
1067
|
-
const { messages } = await (await portOf({ ownerId, chatId })).peekHistory(ownerId, chatId);
|
|
1068
|
-
const answer = messages.find((message) => message.id === pending.messageId);
|
|
1069
|
-
if (!answer) {
|
|
1070
|
-
await store.setDelivery(chatId, null);
|
|
1071
|
-
return null;
|
|
1072
|
-
}
|
|
1073
|
-
const text = answerText(answer.parts);
|
|
1074
|
-
const parts = deliverableParts(answer.parts);
|
|
1075
|
-
const reasons = pending.reasons.map((reason) => JSON.parse(reason));
|
|
1076
|
-
const timeZone = await input.timeZoneOf?.(ownerId, chatId) ?? record.timeZone;
|
|
1077
|
-
return sendAndSettle({ ownerId, chatId, messageId: pending.messageId, text, parts, reasons, attempt: pending.attempt }, { at: serverAt, timeZone });
|
|
1078
|
-
},
|
|
1079
|
-
async report({ chatId, messageId, outcome }) {
|
|
1080
|
-
const record = await store.getChat(chatId);
|
|
1081
|
-
if (!record) return;
|
|
1082
|
-
const { ownerId } = record;
|
|
1083
|
-
if (outcome.status === "delivered" || outcome.status === "retry") return;
|
|
1084
|
-
await markUndelivered(ownerId, chatId, messageId);
|
|
1085
|
-
if (outcome.status === "window_closed") await store.setWindowClosed(chatId, true);
|
|
1086
|
-
if (outcome.status === "blocked") await blocked(ownerId, chatId);
|
|
1087
|
-
else await input.onUndelivered?.({ ownerId, chatId, at: runtime.now() });
|
|
999
|
+
held: renewNow,
|
|
1000
|
+
async stop() {
|
|
1001
|
+
clearInterval(heartbeat);
|
|
1002
|
+
await renewing;
|
|
1088
1003
|
}
|
|
1089
1004
|
};
|
|
1090
1005
|
}
|
|
@@ -1119,7 +1034,7 @@ var TIMER_PARALLEL = 5;
|
|
|
1119
1034
|
var DAY_MS = 864e5;
|
|
1120
1035
|
var DEFAULT_FLOW_SERVER_TURNS_IN_ROW = 50;
|
|
1121
1036
|
var errorText4 = (error) => error instanceof Error ? error.message : String(error);
|
|
1122
|
-
var
|
|
1037
|
+
var isoOf = (at) => new Date(at).toISOString();
|
|
1123
1038
|
var timerKey = (chatId, exitId, run) => `flow:${chatId}:${exitId}:${run.enteredAt}`;
|
|
1124
1039
|
async function historyFacts(port, ownerId, chatId, answerId) {
|
|
1125
1040
|
const { messages } = await port.peekHistory(ownerId, chatId);
|
|
@@ -1145,7 +1060,7 @@ function createFlowTimers(input) {
|
|
|
1145
1060
|
const fired = (chatId, reason) => {
|
|
1146
1061
|
for (const onFired of watchers.get(chatId) ?? []) onFired(reason);
|
|
1147
1062
|
};
|
|
1148
|
-
const dueInput = (due, branchHead, offset) => ({ at:
|
|
1063
|
+
const dueInput = (due, branchHead, offset) => ({ at: isoOf(due.at - offset), exitId: due.exitId, branchHead });
|
|
1149
1064
|
async function placeOf2(record, chatPort, run, failedAt) {
|
|
1150
1065
|
const pausedUntil = record.pausedUntil === null ? null : Date.parse(record.pausedUntil);
|
|
1151
1066
|
const facts = await historyFacts(chatPort, record.ownerId, record.chatId, run.answerId);
|
|
@@ -1170,16 +1085,16 @@ function createFlowTimers(input) {
|
|
|
1170
1085
|
function reschedule(request) {
|
|
1171
1086
|
return oneAtATime(request.chatId, () => rescheduleNow(request));
|
|
1172
1087
|
}
|
|
1173
|
-
async function recountAfterFailure(ownerId, chatId, failedAt) {
|
|
1174
|
-
const { values } = await (await port({ ownerId, chatId })).getChatState(ownerId, chatId);
|
|
1175
|
-
const run = storedRun(values[FLOW_STATE_KEY]);
|
|
1176
|
-
if (run) await reschedule({ ownerId, chatId, run, branchHead: run.answerId ?? null, failedAt });
|
|
1177
|
-
}
|
|
1178
1088
|
async function rescheduleNow({ ownerId, chatId, run, branchHead, failedAt }) {
|
|
1179
1089
|
const record = await store.getChat(chatId);
|
|
1180
1090
|
if (!record) return;
|
|
1181
|
-
|
|
1182
|
-
if (
|
|
1091
|
+
const pending = record.delivery;
|
|
1092
|
+
if (pending) {
|
|
1093
|
+
const due2 = pending.pausedAt === void 0 ? { at: pending.retryAt, exitId: FLOW_DELIVERY_EXIT, branchHead } : null;
|
|
1094
|
+
await store.swapDelivery(chatId, { expected: pending, next: pending, due: due2, freeAt: input.now().toISOString() });
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
if (record.endedAt !== null) return store.setDue(chatId, null, { unlessDelivery: true });
|
|
1183
1098
|
const flow = await input.flowOf(record);
|
|
1184
1099
|
const offset = run.timeOffset ?? 0;
|
|
1185
1100
|
const at = input.now().getTime() + offset;
|
|
@@ -1208,7 +1123,7 @@ function createFlowTimers(input) {
|
|
|
1208
1123
|
});
|
|
1209
1124
|
}
|
|
1210
1125
|
if (applied) await input.onAppliedTransitions?.(applied);
|
|
1211
|
-
await store.setDue(chatId, due && { ...due, branchHead: run.answerId ?? null });
|
|
1126
|
+
await store.setDue(chatId, due && { ...due, branchHead: run.answerId ?? null }, { leaseOwner: owner, unlessDelivery: true });
|
|
1212
1127
|
}
|
|
1213
1128
|
async function placeMoved(chatPort, record, flow, run, at, timeZone) {
|
|
1214
1129
|
const { ownerId, chatId } = record;
|
|
@@ -1225,26 +1140,31 @@ function createFlowTimers(input) {
|
|
|
1225
1140
|
throw error;
|
|
1226
1141
|
}
|
|
1227
1142
|
}
|
|
1228
|
-
async function fire(leased, serverAt) {
|
|
1229
|
-
|
|
1143
|
+
async function fire(leased, serverAt, lease) {
|
|
1144
|
+
const record = leased;
|
|
1230
1145
|
const { chatId, ownerId } = record;
|
|
1146
|
+
const setDue = (due2, options = {}) => store.setDue(chatId, due2, { unlessDelivery: true, ...options, leaseOwner: owner });
|
|
1147
|
+
const pending = record.delivery;
|
|
1148
|
+
if (pending && input.delivery) {
|
|
1149
|
+
if (pending.pausedAt !== void 0) return store.setDue(chatId, null, { leaseOwner: owner });
|
|
1150
|
+
if (Date.parse(pending.retryAt) > serverAt) {
|
|
1151
|
+
await store.setDue(chatId, { at: pending.retryAt, exitId: FLOW_DELIVERY_EXIT, branchHead: record.dueBranchHead }, { leaseOwner: owner });
|
|
1152
|
+
return;
|
|
1153
|
+
}
|
|
1154
|
+
if (!await lease.held()) return;
|
|
1155
|
+
await input.delivery().retry(record, serverAt, owner);
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
if (record.endedAt !== null) {
|
|
1159
|
+
await setDue(null);
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1231
1162
|
const flow = await input.flowOf(record);
|
|
1232
1163
|
if (!flow) {
|
|
1233
1164
|
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId}: \u0432\u0435\u0440\u0441\u0438\u0438 ${record.flowId}@${record.versionId} \u043D\u0435\u0442 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435, \u0442\u0430\u0439\u043C\u0435\u0440 \u0441\u043D\u044F\u0442`);
|
|
1234
|
-
await
|
|
1165
|
+
await setDue(null);
|
|
1235
1166
|
return;
|
|
1236
1167
|
}
|
|
1237
|
-
if (record.delivery && input.delivery) {
|
|
1238
|
-
if (Date.parse(record.delivery.retryAt) > serverAt) {
|
|
1239
|
-
await store.setDue(chatId, { at: record.delivery.retryAt, exitId: FLOW_DELIVERY_EXIT, branchHead: record.dueBranchHead });
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
const outcome2 = await input.delivery().retry(record, serverAt);
|
|
1243
|
-
const after = await store.getChat(chatId);
|
|
1244
|
-
if (!after || after.endedAt !== null || after.delivery) return;
|
|
1245
|
-
if (outcome2 === "failed" || outcome2 === "window_closed") return recountAfterFailure(ownerId, chatId, serverAt);
|
|
1246
|
-
record = after;
|
|
1247
|
-
}
|
|
1248
1168
|
let chatPort;
|
|
1249
1169
|
let values;
|
|
1250
1170
|
let offset;
|
|
@@ -1253,7 +1173,7 @@ function createFlowTimers(input) {
|
|
|
1253
1173
|
({ values } = await chatPort.getChatState(ownerId, chatId));
|
|
1254
1174
|
offset = testChatOffset(await chatPort.getChatData(ownerId, chatId)) ?? 0;
|
|
1255
1175
|
} catch (error) {
|
|
1256
|
-
if (!(error instanceof
|
|
1176
|
+
if (!(error instanceof ChatNotFoundError)) throw error;
|
|
1257
1177
|
await store.deleteChat(chatId);
|
|
1258
1178
|
return;
|
|
1259
1179
|
}
|
|
@@ -1262,12 +1182,13 @@ function createFlowTimers(input) {
|
|
|
1262
1182
|
const { run, context, undelivered } = await placeOf2(record, chatPort, await withApplied(record, flow, chatRun(flow, values, record, at, timeZone), at, timeZone));
|
|
1263
1183
|
const wake = nextWake(flow, run, at, timeZone, context);
|
|
1264
1184
|
if (!wake || wake.at > at) {
|
|
1265
|
-
await
|
|
1185
|
+
await setDue(wake ? dueInput(wake, run.answerId ?? null, offset) : null);
|
|
1266
1186
|
return;
|
|
1267
1187
|
}
|
|
1268
1188
|
const due = dueReasons(flow, run, at, timeZone, context);
|
|
1269
1189
|
if (due.deadline) {
|
|
1270
|
-
await
|
|
1190
|
+
if (!await lease.held()) return;
|
|
1191
|
+
await setDue(null);
|
|
1271
1192
|
const endAt = flow.schedule ? deadlineAt(run, timeZone, flow.schedule) ?? wake.at : wake.at;
|
|
1272
1193
|
await input.onDeadline?.({ record, flow, run, at: endAt, timeZone });
|
|
1273
1194
|
const deadlineState = flow.schedule?.deadlineState;
|
|
@@ -1275,7 +1196,8 @@ function createFlowTimers(input) {
|
|
|
1275
1196
|
return;
|
|
1276
1197
|
}
|
|
1277
1198
|
if (due.giveUp) {
|
|
1278
|
-
await
|
|
1199
|
+
if (!await lease.held()) return;
|
|
1200
|
+
await setDue(null);
|
|
1279
1201
|
await input.onGiveUp?.({ record, flow, run, at });
|
|
1280
1202
|
fired(chatId, { kind: "give_up", stateId: run.state });
|
|
1281
1203
|
return;
|
|
@@ -1292,8 +1214,9 @@ function createFlowTimers(input) {
|
|
|
1292
1214
|
const reasons = [...moved ? [{ kind: "transition", stateId: next.state }] : [], ...due.reasons];
|
|
1293
1215
|
const keep = async (wakeAt, applyNow) => {
|
|
1294
1216
|
const deferred = wakeAt && dueInput(wakeAt, run.answerId ?? null, offset);
|
|
1217
|
+
if (moved && !await lease.held()) return;
|
|
1295
1218
|
if (moved) await keepForNextTurn(chatId, run, transitions[0], deferred, applyNow ? { record, flow, run: next, transitions, at } : null);
|
|
1296
|
-
else await
|
|
1219
|
+
else await setDue(deferred);
|
|
1297
1220
|
};
|
|
1298
1221
|
const defer = (retryAt) => keep(deferredWake(flow, next, at, timeZone, context, { at: retryAt, exitId: moved && retryAt !== null ? due.exitId : FLOW_FIRST_EXIT }), retryAt === null);
|
|
1299
1222
|
if (reasons.length === 0) {
|
|
@@ -1330,6 +1253,7 @@ function createFlowTimers(input) {
|
|
|
1330
1253
|
const notice = undelivered ? { ...reasonsNotice, data: { ...reasonsNotice.data, undelivered } } : reasonsNotice;
|
|
1331
1254
|
const reply = moved ? exactFirstText(flow, next.state) : null;
|
|
1332
1255
|
const delivery = input.delivery?.();
|
|
1256
|
+
if (!await lease.held()) return;
|
|
1333
1257
|
const result = await chatPort.deliverNotices({
|
|
1334
1258
|
ownerId,
|
|
1335
1259
|
chatId,
|
|
@@ -1357,27 +1281,23 @@ function createFlowTimers(input) {
|
|
|
1357
1281
|
const { nudgeStep, nudgeStepOwn } = notice.data;
|
|
1358
1282
|
const answered = nudgeStep === void 0 ? noteAgentAnswer(next, at) : withNudgeStep(noteAgentAnswer(next, at), nudgeStep, nudgeStepOwn === true);
|
|
1359
1283
|
const following = nextWake(flow, answered, at, timeZone, { pausedUntil: context.pausedUntil ?? null });
|
|
1360
|
-
await
|
|
1284
|
+
await setDue(following ? dueInput(following, result.messageId ?? null, offset) : null, { unlessDelivery: true });
|
|
1361
1285
|
fired(chatId, { kind: reasons[0].kind, stateId: next.state, ...moved && { exitId: due.exitId }, reasons });
|
|
1362
1286
|
if (!delivery) {
|
|
1363
1287
|
await firstMessages?.record({ ownerId, at, timeZone });
|
|
1364
1288
|
return;
|
|
1365
1289
|
}
|
|
1366
|
-
|
|
1367
|
-
if (outcome === "failed" || outcome === "window_closed") await recountAfterFailure(ownerId, chatId, serverAt);
|
|
1290
|
+
await delivery.afterFirstTurn({ ownerId, chatId, answer: result.answer, reasons, at, timeZone, endedAt: record.endedAt });
|
|
1368
1291
|
}
|
|
1369
1292
|
const renew = (chatId) => store.renewLease({ chatId, owner, now: input.now().toISOString(), leaseMs });
|
|
1370
1293
|
async function fireLeased(record, serverAt) {
|
|
1371
1294
|
try {
|
|
1372
1295
|
if (!await renew(record.chatId)) return;
|
|
1373
|
-
const
|
|
1374
|
-
renew(record.chatId).catch((error) => logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${record.chatId} \u0430\u0440\u0435\u043D\u0434\u0430 \u043D\u0435 \u043F\u0440\u043E\u0434\u043B\u0435\u043D\u0430: ${errorText4(error)}`));
|
|
1375
|
-
}, leaseMs / 3);
|
|
1376
|
-
heartbeat.unref?.();
|
|
1296
|
+
const lease = holdLease({ store, chatId: record.chatId, owner, leaseMs, now: input.now, logError });
|
|
1377
1297
|
try {
|
|
1378
|
-
await fire(record, serverAt);
|
|
1298
|
+
await fire(record, serverAt, lease);
|
|
1379
1299
|
} finally {
|
|
1380
|
-
|
|
1300
|
+
await lease.stop();
|
|
1381
1301
|
}
|
|
1382
1302
|
} catch (error) {
|
|
1383
1303
|
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${record.chatId} \u0442\u0430\u0439\u043C\u0435\u0440 \u043D\u0435 \u0441\u0440\u0430\u0431\u043E\u0442\u0430\u043B: ${errorText4(error)}`);
|
|
@@ -1433,12 +1353,12 @@ function createChatMigrator(deps) {
|
|
|
1433
1353
|
async function newVersion(flowId, versionId, raw) {
|
|
1434
1354
|
if (raw !== void 0) {
|
|
1435
1355
|
const loaded = { flowId, versionId, flow: raw };
|
|
1436
|
-
const
|
|
1437
|
-
if (!
|
|
1356
|
+
const flow2 = deps.checkedFlow(loaded);
|
|
1357
|
+
if (!flow2) throw new Error(`\u0421\u0446\u0435\u043D\u0430\u0440\u0438\u0439 ${flowId}@${versionId} \u043D\u0435 \u0440\u0430\u0437\u043E\u0431\u0440\u0430\u043D \u0438\u043B\u0438 \u0441 \u043E\u0448\u0438\u0431\u043A\u0430\u043C\u0438: \u0447\u0430\u0442\u044B \u043D\u0435 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u044B.`);
|
|
1438
1358
|
return {
|
|
1439
|
-
flow,
|
|
1359
|
+
flow: flow2,
|
|
1440
1360
|
save: async () => {
|
|
1441
|
-
if (await deps.saveVersion(loaded,
|
|
1361
|
+
if (await deps.saveVersion(loaded, flow2) === "conflict") {
|
|
1442
1362
|
throw new Error(`\u041F\u043E\u0434 \u043D\u043E\u043C\u0435\u0440\u043E\u043C ${versionId} \u0443\u0436\u0435 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D \u0434\u0440\u0443\u0433\u043E\u0439 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \xAB${flowId}\xBB: \u0432\u043E\u0437\u044C\u043C\u0438\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u043D\u043E\u043C\u0435\u0440 \u0432\u0435\u0440\u0441\u0438\u0438.`);
|
|
1443
1363
|
}
|
|
1444
1364
|
}
|
|
@@ -1446,7 +1366,9 @@ function createChatMigrator(deps) {
|
|
|
1446
1366
|
}
|
|
1447
1367
|
const found = await store.getVersion(flowId, versionId);
|
|
1448
1368
|
if (!found) throw new Error(`\u0412\u0435\u0440\u0441\u0438\u0438 ${versionId} \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \xAB${flowId}\xBB \u043D\u0435\u0442 \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: \u043F\u0435\u0440\u0435\u0434\u0430\u0439\u0442\u0435 \u0435\u0451 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439 \u0432 \u043F\u043E\u043B\u0435 flow.`);
|
|
1449
|
-
|
|
1369
|
+
const flow = deps.checkedFlow({ flowId, versionId, flow: found.flow });
|
|
1370
|
+
if (!flow) throw new Error(`\u0421\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F ${flowId}@${versionId} \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443: \u0447\u0430\u0442\u044B \u043D\u0435 \u043F\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043D\u044B.`);
|
|
1371
|
+
return { flow };
|
|
1450
1372
|
}
|
|
1451
1373
|
async function selectedChats(flowId, exceptVersionId, selection) {
|
|
1452
1374
|
const rows = await store.listFlowChats({ flowId, ...exceptVersionId !== void 0 && { exceptVersionId }, ...selection.chatIds && { chatIds: selection.chatIds } });
|
|
@@ -1508,7 +1430,7 @@ function createChatMigrator(deps) {
|
|
|
1508
1430
|
|
|
1509
1431
|
// src/runtime/flow-signals.ts
|
|
1510
1432
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
1511
|
-
import { ChatNotFoundError as
|
|
1433
|
+
import { ChatNotFoundError as ChatNotFoundError2 } from "@ykurilov/agent-chat-core";
|
|
1512
1434
|
async function sendFlowSignal(input) {
|
|
1513
1435
|
const { runtime, port, signal } = input;
|
|
1514
1436
|
const { ownerId, chatId, name } = signal;
|
|
@@ -1524,7 +1446,7 @@ async function sendFlowSignal(input) {
|
|
|
1524
1446
|
try {
|
|
1525
1447
|
({ values } = await port.getChatState(ownerId, chatId));
|
|
1526
1448
|
} catch (error) {
|
|
1527
|
-
if (error instanceof
|
|
1449
|
+
if (error instanceof ChatNotFoundError2) return;
|
|
1528
1450
|
throw error;
|
|
1529
1451
|
}
|
|
1530
1452
|
const transitions = [];
|
|
@@ -2019,13 +1941,243 @@ function createFlowPause({
|
|
|
2019
1941
|
};
|
|
2020
1942
|
}
|
|
2021
1943
|
|
|
1944
|
+
// src/runtime/flow-delivery.ts
|
|
1945
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
1946
|
+
import { answerText, ChatNotFoundError as ChatNotFoundError3, deliverableParts, isHumanMessage } from "@ykurilov/agent-chat-core";
|
|
1947
|
+
var RETRY_DELAYS_MS = [6e4, 5 * 6e4, 30 * 6e4];
|
|
1948
|
+
var pauseOf = (attempt) => RETRY_DELAYS_MS[Math.min(attempt, RETRY_DELAYS_MS.length) - 1];
|
|
1949
|
+
var errorText5 = (error) => error instanceof Error ? error.message : String(error);
|
|
1950
|
+
var isoOf2 = (at) => new Date(at).toISOString();
|
|
1951
|
+
var hasContent = (answer) => answer.text.trim() !== "" || answer.parts.some((part) => part.type !== "text");
|
|
1952
|
+
function createFlowDelivery(input) {
|
|
1953
|
+
const { runtime } = input;
|
|
1954
|
+
const store = runtime.store;
|
|
1955
|
+
const portOf = (chat) => {
|
|
1956
|
+
if (!input.port) throw new Error("\u0414\u043E\u0441\u0442\u0430\u0432\u043A\u0430 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F \u0441\u043E\u0431\u0440\u0430\u043D\u0430 \u0431\u0435\u0437 \u0441\u043B\u0443\u0436\u0431\u044B \u0447\u0430\u0442\u0430");
|
|
1957
|
+
return input.port(chat);
|
|
1958
|
+
};
|
|
1959
|
+
async function recount(chat) {
|
|
1960
|
+
try {
|
|
1961
|
+
await input.recount?.(chat);
|
|
1962
|
+
} catch (error) {
|
|
1963
|
+
runtime.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chat.chatId} \u0441\u0440\u043E\u043A\u0438 \u043F\u043E\u0441\u043B\u0435 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u043D\u044B: ${errorText5(error)}`);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
const writer = `flow-delivery:${randomUUID4()}`;
|
|
1967
|
+
let written = 0;
|
|
1968
|
+
const nextToken = () => `${writer}:d${++written}`;
|
|
1969
|
+
async function markUndelivered(ownerId, chatId, messageId) {
|
|
1970
|
+
try {
|
|
1971
|
+
await (await portOf({ ownerId, chatId })).markUndelivered({ ownerId, chatId, messageId });
|
|
1972
|
+
} catch (error) {
|
|
1973
|
+
if (error instanceof ChatNotFoundError3) return;
|
|
1974
|
+
runtime.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u043F\u043E\u043C\u0435\u0442\u043A\u0430 \xAB\u043D\u0435 \u0434\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E\xBB \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u0430: ${errorText5(error)}`);
|
|
1975
|
+
}
|
|
1976
|
+
}
|
|
1977
|
+
async function blocked(ownerId, chatId) {
|
|
1978
|
+
const pinned = await runtime.chatFlow(ownerId, chatId);
|
|
1979
|
+
if (!pinned || !input.finish) {
|
|
1980
|
+
await store.endChat(chatId, { reason: "blocked", at: isoOf2(runtime.now()) });
|
|
1981
|
+
return;
|
|
1982
|
+
}
|
|
1983
|
+
const { values } = await (await portOf({ ownerId, chatId })).getChatState(ownerId, chatId);
|
|
1984
|
+
const at = runtime.now();
|
|
1985
|
+
await input.finish().end({ ownerId, chatId, pinned, run: await runOutsideTurn(runtime, { ownerId, chatId }, pinned.flow, values, at), reason: "blocked", at });
|
|
1986
|
+
}
|
|
1987
|
+
async function staleness(record, pending) {
|
|
1988
|
+
let messages;
|
|
1989
|
+
try {
|
|
1990
|
+
({ messages } = await (await portOf(record)).peekHistory(record.ownerId, record.chatId));
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
if (error instanceof ChatNotFoundError3) return { stale: true };
|
|
1993
|
+
throw error;
|
|
1994
|
+
}
|
|
1995
|
+
const index = messages.findIndex((message) => message.id === pending.messageId);
|
|
1996
|
+
if (index < 0) return { stale: true };
|
|
1997
|
+
const answer = messages[index];
|
|
1998
|
+
const repaired = pending.resumedAt !== void 0;
|
|
1999
|
+
return { stale: repaired && messages.slice(index + 1).some(isHumanMessage), answer };
|
|
2000
|
+
}
|
|
2001
|
+
async function drop(record, pending, answerId, freeAt) {
|
|
2002
|
+
const { ownerId, chatId } = record;
|
|
2003
|
+
if (!await store.swapDelivery(chatId, { expected: pending, next: null, ...freeAt && { freeAt } })) return false;
|
|
2004
|
+
if (answerId) await markUndelivered(ownerId, chatId, answerId);
|
|
2005
|
+
await recount({ ownerId, chatId, ...answerId && { failedAt: runtime.now() } });
|
|
2006
|
+
return true;
|
|
2007
|
+
}
|
|
2008
|
+
async function settle(reservation, message, outcome, job) {
|
|
2009
|
+
const { ownerId, chatId, messageId } = message;
|
|
2010
|
+
const swap = (next, due) => store.swapDelivery(chatId, {
|
|
2011
|
+
expected: reservation,
|
|
2012
|
+
next,
|
|
2013
|
+
...due !== void 0 && { due: due && { at: due.at, exitId: FLOW_DELIVERY_EXIT, branchHead: job.branchHead } }
|
|
2014
|
+
});
|
|
2015
|
+
switch (outcome.status) {
|
|
2016
|
+
case "delivered":
|
|
2017
|
+
if (!await swap(null)) return;
|
|
2018
|
+
await input.firstMessages?.()?.record({ ownerId, ...job.counted });
|
|
2019
|
+
await recount({ ownerId, chatId });
|
|
2020
|
+
return;
|
|
2021
|
+
case "retry": {
|
|
2022
|
+
if (message.attempt > RETRY_DELAYS_MS.length) return settle(reservation, message, { status: "failed" }, job);
|
|
2023
|
+
const retryAt = isoOf2(runtime.now() + (outcome.afterMs ?? RETRY_DELAYS_MS[message.attempt - 1]));
|
|
2024
|
+
const { resumedAt } = reservation;
|
|
2025
|
+
await swap({ messageId, attempt: message.attempt + 1, retryAt, reasons: reservation.reasons, token: nextToken(), ...resumedAt && { resumedAt } }, { at: retryAt });
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
case "await_repair": {
|
|
2029
|
+
const { resumedAt: _resumedAt, ...rest } = reservation;
|
|
2030
|
+
await swap({ ...rest, token: nextToken(), pausedAt: isoOf2(runtime.now()) }, null);
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
case "failed":
|
|
2034
|
+
case "window_closed":
|
|
2035
|
+
if (!await swap(null)) return;
|
|
2036
|
+
await markUndelivered(ownerId, chatId, messageId);
|
|
2037
|
+
if (outcome.status === "window_closed") await store.setWindowClosed(chatId, true);
|
|
2038
|
+
await recount({ ownerId, chatId, failedAt: runtime.now() });
|
|
2039
|
+
return;
|
|
2040
|
+
case "blocked":
|
|
2041
|
+
if (!await swap(null)) return;
|
|
2042
|
+
await markUndelivered(ownerId, chatId, messageId);
|
|
2043
|
+
await blocked(ownerId, chatId);
|
|
2044
|
+
return;
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
async function send(message) {
|
|
2048
|
+
const deliver = input.deliver();
|
|
2049
|
+
if (!deliver) throw new Error("\u0423 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F \u043D\u0435\u0442 \u0441\u0432\u043E\u0435\u0439 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438 (attach({ deliver }))");
|
|
2050
|
+
try {
|
|
2051
|
+
return await deliver(message);
|
|
2052
|
+
} catch (error) {
|
|
2053
|
+
runtime.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${message.chatId} \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430 \u0443\u043F\u0430\u043B\u0430: ${errorText5(error)}`);
|
|
2054
|
+
return { status: "retry" };
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
async function lastCheck(chatId, reservation, endedAt, ready) {
|
|
2058
|
+
const record = await store.getChat(chatId);
|
|
2059
|
+
if (!record || !sameDelivery(record.delivery, reservation)) return { replaced: true };
|
|
2060
|
+
if (endedAt !== void 0 && record.endedAt !== endedAt) return { stale: true, answerId: reservation.messageId };
|
|
2061
|
+
return ready(record);
|
|
2062
|
+
}
|
|
2063
|
+
async function dispatch(job) {
|
|
2064
|
+
const { record, expected, attempt } = job;
|
|
2065
|
+
const { ownerId, chatId } = record;
|
|
2066
|
+
const branchHead = record.dueBranchHead;
|
|
2067
|
+
const now = runtime.now();
|
|
2068
|
+
const reservation = {
|
|
2069
|
+
messageId: attempt.messageId,
|
|
2070
|
+
attempt: attempt.attempt,
|
|
2071
|
+
retryAt: isoOf2(now + pauseOf(attempt.attempt)),
|
|
2072
|
+
reasons: attempt.reasons,
|
|
2073
|
+
token: nextToken(),
|
|
2074
|
+
...expected?.resumedAt && { resumedAt: expected.resumedAt }
|
|
2075
|
+
};
|
|
2076
|
+
const due = (at) => ({ at, exitId: FLOW_DELIVERY_EXIT, branchHead });
|
|
2077
|
+
const claim = { owner: reservation.token, now: isoOf2(now), leaseMs: input.leaseMs, ...job.from && { from: job.from } };
|
|
2078
|
+
const sameEnd = job.endedAt !== void 0 ? { endedAt: job.endedAt } : {};
|
|
2079
|
+
if (!await store.swapDelivery(chatId, { expected, next: reservation, due: due(reservation.retryAt), claim, ...sameEnd })) {
|
|
2080
|
+
const queued = { ...reservation, retryAt: isoOf2(now), token: nextToken() };
|
|
2081
|
+
if (await store.swapDelivery(chatId, { expected, next: queued, due: due(queued.retryAt), ...sameEnd })) return;
|
|
2082
|
+
if (expected === null) {
|
|
2083
|
+
runtime.logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u043E\u0442\u0432\u0435\u0442 ${attempt.messageId} \u043D\u0435 \u043E\u0442\u043F\u0440\u0430\u0432\u043B\u0435\u043D: \u0436\u0434\u0451\u0442 \u0434\u0440\u0443\u0433\u0430\u044F \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430 \u0438\u043B\u0438 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0437\u0430\u043A\u043E\u043D\u0447\u0435\u043D`);
|
|
2084
|
+
await markUndelivered(ownerId, chatId, attempt.messageId);
|
|
2085
|
+
}
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
const lease = holdLease({ store, chatId, owner: reservation.token, leaseMs: input.leaseMs, now: () => new Date(runtime.now()), logError: runtime.logError });
|
|
2089
|
+
try {
|
|
2090
|
+
let ready;
|
|
2091
|
+
let outcome;
|
|
2092
|
+
try {
|
|
2093
|
+
ready = await lastCheck(chatId, reservation, job.endedAt, job.ready);
|
|
2094
|
+
if ("message" in ready) outcome = await send(ready.message);
|
|
2095
|
+
} finally {
|
|
2096
|
+
await lease.stop();
|
|
2097
|
+
}
|
|
2098
|
+
if ("message" in ready) await settle(reservation, ready.message, outcome, { counted: job.counted, branchHead });
|
|
2099
|
+
else if ("stale" in ready) await drop(record, reservation, ready.answerId);
|
|
2100
|
+
} finally {
|
|
2101
|
+
await store.releaseLease(chatId, reservation.token);
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
own: () => Boolean(input.deliver()),
|
|
2106
|
+
async afterFirstTurn({ ownerId, chatId, answer, reasons, at, timeZone, endedAt }) {
|
|
2107
|
+
if (!input.deliver()) {
|
|
2108
|
+
await input.firstMessages?.()?.record({ ownerId, at, timeZone });
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
2111
|
+
if (!answer || answer.status !== "complete" || !hasContent(answer)) return;
|
|
2112
|
+
const record = await store.getChat(chatId);
|
|
2113
|
+
if (!record) return;
|
|
2114
|
+
if (record.endedAt !== endedAt) {
|
|
2115
|
+
await markUndelivered(ownerId, chatId, answer.messageId);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
const message = { ownerId, chatId, messageId: answer.messageId, text: answer.text, parts: answer.parts, reasons, attempt: 1 };
|
|
2119
|
+
const attempt = { messageId: message.messageId, attempt: 1, reasons: reasons.map((reason) => JSON.stringify(reason)) };
|
|
2120
|
+
await dispatch({ record, expected: null, attempt, endedAt, ready: async () => ({ message }), counted: { at, timeZone } });
|
|
2121
|
+
},
|
|
2122
|
+
async retry(record, serverAt, from) {
|
|
2123
|
+
const pending = record.delivery;
|
|
2124
|
+
if (!pending) return;
|
|
2125
|
+
const { ownerId, chatId } = record;
|
|
2126
|
+
const timeZone = await input.timeZoneOf?.(ownerId, chatId) ?? record.timeZone;
|
|
2127
|
+
const reasons = pending.reasons.map((reason) => JSON.parse(reason));
|
|
2128
|
+
const ready = async (current) => {
|
|
2129
|
+
const { stale, answer } = await staleness(current, current.delivery);
|
|
2130
|
+
if (stale || !answer) return { stale: true, ...answer && { answerId: answer.id } };
|
|
2131
|
+
const message = { ownerId, chatId, messageId: pending.messageId, text: answerText(answer.parts), parts: deliverableParts(answer.parts), reasons, attempt: pending.attempt };
|
|
2132
|
+
return { message };
|
|
2133
|
+
};
|
|
2134
|
+
const attempt = { messageId: pending.messageId, attempt: pending.attempt, reasons: pending.reasons };
|
|
2135
|
+
await dispatch({ record, expected: pending, attempt, ready, counted: { at: serverAt, timeZone }, from });
|
|
2136
|
+
},
|
|
2137
|
+
async resume({ ownerId, chatId, messageId }) {
|
|
2138
|
+
for (let round = 0; round < 3; round += 1) {
|
|
2139
|
+
const record = await store.getChat(chatId);
|
|
2140
|
+
const pending = record?.delivery;
|
|
2141
|
+
if (!record || record.ownerId !== ownerId || !pending || pending.messageId !== messageId) return "not_found";
|
|
2142
|
+
if (pending.pausedAt === void 0) return "pending";
|
|
2143
|
+
const at = isoOf2(runtime.now());
|
|
2144
|
+
const { stale, answer } = await staleness(record, { ...pending, resumedAt: at });
|
|
2145
|
+
if (stale) {
|
|
2146
|
+
if (await drop(record, pending, answer?.id, at)) return "dropped";
|
|
2147
|
+
} else {
|
|
2148
|
+
const next = { messageId, attempt: pending.attempt, retryAt: at, reasons: pending.reasons, token: nextToken(), resumedAt: at };
|
|
2149
|
+
const due = { at, exitId: FLOW_DELIVERY_EXIT, branchHead: record.dueBranchHead };
|
|
2150
|
+
if (await store.swapDelivery(chatId, { expected: pending, next, due, freeAt: at })) return "resumed";
|
|
2151
|
+
}
|
|
2152
|
+
if (sameDelivery((await store.getChat(chatId))?.delivery ?? null, pending)) return "busy";
|
|
2153
|
+
}
|
|
2154
|
+
return "busy";
|
|
2155
|
+
},
|
|
2156
|
+
async report({ chatId, messageId, outcome }) {
|
|
2157
|
+
if (outcome.status === "await_repair") {
|
|
2158
|
+
throw new TypeError("reportDelivery: \u0438\u0441\u0445\u043E\u0434 await_repair \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0441\u0432\u043E\u0435\u0439 \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0438 attach({ deliver }); \u043A\u0430\u043D\u0430\u043B\u044B SDK \u043F\u043E\u0432\u0442\u043E\u0440\u044F\u044E\u0442 \u0441\u0430\u043C\u0438");
|
|
2159
|
+
}
|
|
2160
|
+
const record = await store.getChat(chatId);
|
|
2161
|
+
if (!record) return;
|
|
2162
|
+
const { ownerId } = record;
|
|
2163
|
+
if (outcome.status === "delivered" || outcome.status === "retry") return;
|
|
2164
|
+
await markUndelivered(ownerId, chatId, messageId);
|
|
2165
|
+
if (outcome.status === "window_closed") await store.setWindowClosed(chatId, true);
|
|
2166
|
+
if (outcome.status === "blocked") {
|
|
2167
|
+
await store.setDelivery(chatId, null);
|
|
2168
|
+
await blocked(ownerId, chatId);
|
|
2169
|
+
} else await recount({ ownerId, chatId, failedAt: runtime.now() });
|
|
2170
|
+
}
|
|
2171
|
+
};
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2022
2174
|
// src/runtime/agent-flow.ts
|
|
2023
2175
|
var JUDGE_HISTORY = 6;
|
|
2024
2176
|
var DELIVERY_RETRY_MS = [50, 250, 1e3, 3e3];
|
|
2025
2177
|
var KNOWN_FLOWS_LIMIT = 1e4;
|
|
2026
2178
|
var KNOWN_PORTS_LIMIT = 1e4;
|
|
2027
2179
|
var SERVER_UNKNOWN_CODES = /* @__PURE__ */ new Set(["unknown_event"]);
|
|
2028
|
-
var
|
|
2180
|
+
var errorText6 = (error) => error instanceof Error ? error.message : String(error);
|
|
2029
2181
|
var pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2030
2182
|
function stableJson(value) {
|
|
2031
2183
|
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
@@ -2050,6 +2202,9 @@ function turnWindow(messages, run, answer) {
|
|
|
2050
2202
|
}
|
|
2051
2203
|
function buildAgentFlow(options, modules = {}) {
|
|
2052
2204
|
const { store } = options;
|
|
2205
|
+
if (store?.guarded !== true) {
|
|
2206
|
+
throw new TypeError("createAgentFlow: \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u0432 \u0431\u0435\u0437 guarded: true \u2014 \u0441 0.81 \u0441\u0432\u043E\u0451 FlowStore \u0434\u043E\u043B\u0436\u043D\u043E \u0432\u044B\u043F\u043E\u043B\u043D\u044F\u0442\u044C \u0443\u0441\u043B\u043E\u0432\u0438\u044F setDue/swapDelivery/leaseDue/endChat (\u0441\u043C. \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 0.81)");
|
|
2207
|
+
}
|
|
2053
2208
|
const clock = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
2054
2209
|
const now = () => clock().getTime();
|
|
2055
2210
|
const iso = () => clock().toISOString();
|
|
@@ -2101,7 +2256,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2101
2256
|
try {
|
|
2102
2257
|
return make();
|
|
2103
2258
|
} catch (error) {
|
|
2104
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] ${name} \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${
|
|
2259
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] ${name} \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D: ${errorText6(error)}`);
|
|
2105
2260
|
return null;
|
|
2106
2261
|
}
|
|
2107
2262
|
});
|
|
@@ -2166,7 +2321,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2166
2321
|
);
|
|
2167
2322
|
const background = /* @__PURE__ */ new Set();
|
|
2168
2323
|
function track(work) {
|
|
2169
|
-
const tracked = work.catch((error) => logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0444\u043E\u043D\u043E\u0432\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u0443\u043F\u0430\u043B\u0430: ${
|
|
2324
|
+
const tracked = work.catch((error) => logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0444\u043E\u043D\u043E\u0432\u0430\u044F \u0440\u0430\u0431\u043E\u0442\u0430 \u0443\u043F\u0430\u043B\u0430: ${errorText6(error)}`)).finally(() => background.delete(tracked));
|
|
2170
2325
|
background.add(tracked);
|
|
2171
2326
|
}
|
|
2172
2327
|
const known = /* @__PURE__ */ new Map();
|
|
@@ -2183,8 +2338,10 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2183
2338
|
let found = versions.get(key);
|
|
2184
2339
|
if (!found) {
|
|
2185
2340
|
found = store.getVersion(flowId, versionId).then((saved) => {
|
|
2186
|
-
if (saved)
|
|
2187
|
-
|
|
2341
|
+
if (!saved) return null;
|
|
2342
|
+
const problem = confirmationProblem(flowId, versionId, saved.flow);
|
|
2343
|
+
if (problem) logError(problem);
|
|
2344
|
+
return problem ? null : saved.flow;
|
|
2188
2345
|
});
|
|
2189
2346
|
versions.set(key, found);
|
|
2190
2347
|
void found.then((flow) => flow ?? versions.delete(key), () => versions.delete(key));
|
|
@@ -2197,14 +2354,21 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2197
2354
|
const catalog = { tools: parsed.flow.tools, events: [], widgets: [] };
|
|
2198
2355
|
const errors = checkFlow(parsed.flow, catalog).filter((issue) => issue.level === "error" && !SERVER_UNKNOWN_CODES.has(issue.code));
|
|
2199
2356
|
if (errors.length > 0) return { problem: `[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] ${flowId}@${versionId} \u0441 \u043E\u0448\u0438\u0431\u043A\u0430\u043C\u0438, \u0447\u0430\u0442 \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: ${errors.map((issue) => issue.message).join("; ")}` };
|
|
2200
|
-
|
|
2357
|
+
const problem = confirmationProblem(flowId, versionId, parsed.flow);
|
|
2358
|
+
return problem ? { problem } : { flow: parsed.flow };
|
|
2359
|
+
}
|
|
2360
|
+
function confirmationProblem(flowId, versionId, flow) {
|
|
2361
|
+
try {
|
|
2362
|
+
confirmations.validate(flowId, flow);
|
|
2363
|
+
return null;
|
|
2364
|
+
} catch (error) {
|
|
2365
|
+
if (!(error instanceof TypeError)) throw error;
|
|
2366
|
+
return `[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] ${flowId}@${versionId} \u043D\u0435 \u0441\u0445\u043E\u0434\u0438\u0442\u0441\u044F \u0441 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F\u043C\u0438 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u044F, \u0447\u0430\u0442 \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F: ${error.message}`;
|
|
2367
|
+
}
|
|
2201
2368
|
}
|
|
2202
2369
|
function checkedFlow(flowId, versionId, value) {
|
|
2203
2370
|
const checked = flowOrProblem(flowId, versionId, value);
|
|
2204
|
-
if ("flow" in checked)
|
|
2205
|
-
confirmations.validate(flowId, checked.flow);
|
|
2206
|
-
return checked.flow;
|
|
2207
|
-
}
|
|
2371
|
+
if ("flow" in checked) return checked.flow;
|
|
2208
2372
|
logError(checked.problem);
|
|
2209
2373
|
return null;
|
|
2210
2374
|
}
|
|
@@ -2227,7 +2391,9 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2227
2391
|
if (loaded.stateId !== void 0 && !stateById(flow, loaded.stateId)) {
|
|
2228
2392
|
return void fail(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId}: \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F \xAB${loaded.stateId}\xBB \u043D\u0435\u0442 \u0432 ${loaded.flowId}@${loaded.versionId}, \u0447\u0430\u0442 \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F`), null;
|
|
2229
2393
|
}
|
|
2230
|
-
await saveChecked(loaded, flow)
|
|
2394
|
+
if (await saveChecked(loaded, flow) === "conflict" && !await versionFlow(loaded.flowId, loaded.versionId)) {
|
|
2395
|
+
return void fail(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId}: \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D\u043D\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F ${loaded.flowId}@${loaded.versionId} \u043D\u0435 \u043F\u0440\u043E\u0448\u043B\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443, \u0447\u0430\u0442 \u0438\u0434\u0451\u0442 \u0431\u0435\u0437 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u044F`), null;
|
|
2396
|
+
}
|
|
2231
2397
|
const at = now();
|
|
2232
2398
|
const startedAt = loaded.startedAt ?? new Date(at);
|
|
2233
2399
|
const { lastHumanAt, lastAgentAt, nudgeStep, today } = loaded;
|
|
@@ -2345,7 +2511,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2345
2511
|
return Object.fromEntries(entries.map((entry) => [entry.fieldId, entry.value]));
|
|
2346
2512
|
} catch (error) {
|
|
2347
2513
|
if (!app) throw error;
|
|
2348
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430: ${
|
|
2514
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u043A\u0430\u0440\u0442\u043E\u0447\u043A\u0430 \u043A\u043B\u0438\u0435\u043D\u0442\u0430 \u043D\u0435 \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u043D\u0430: ${errorText6(error)}`);
|
|
2349
2515
|
return {};
|
|
2350
2516
|
}
|
|
2351
2517
|
},
|
|
@@ -2354,7 +2520,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2354
2520
|
try {
|
|
2355
2521
|
await store.updateChat(chatId, { stateId: run.state }, iso());
|
|
2356
2522
|
} catch (error) {
|
|
2357
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: ${
|
|
2523
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: ${errorText6(error)}`);
|
|
2358
2524
|
}
|
|
2359
2525
|
const { schedule } = pinned.flow;
|
|
2360
2526
|
const timeZone = schedule ? await timeZones.of(ownerId, chatId) : null;
|
|
@@ -2372,11 +2538,12 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2372
2538
|
() => createFlowDelivery({
|
|
2373
2539
|
runtime,
|
|
2374
2540
|
deliver: () => attachment?.deliver,
|
|
2541
|
+
leaseMs: options.timers?.leaseMs ?? DEFAULT_TIMER_LEASE_MS,
|
|
2375
2542
|
port: portOf,
|
|
2376
2543
|
finish,
|
|
2377
2544
|
firstMessages,
|
|
2378
2545
|
timeZoneOf: (ownerId, chatId) => timeZones.of(ownerId, chatId),
|
|
2379
|
-
|
|
2546
|
+
recount: recountFromState
|
|
2380
2547
|
})
|
|
2381
2548
|
);
|
|
2382
2549
|
async function history(ownerId, chatId, turn) {
|
|
@@ -2387,13 +2554,13 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2387
2554
|
try {
|
|
2388
2555
|
await optionalTimers()?.reschedule({ ownerId, chatId, run, branchHead, ...failedAt !== void 0 && { failedAt } });
|
|
2389
2556
|
} catch (error) {
|
|
2390
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0442\u0430\u0439\u043C\u0435\u0440 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u043D: ${
|
|
2557
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0442\u0430\u0439\u043C\u0435\u0440 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u043D: ${errorText6(error)}`);
|
|
2391
2558
|
}
|
|
2392
2559
|
}
|
|
2393
|
-
async function
|
|
2560
|
+
async function recountFromState({ ownerId, chatId, failedAt }) {
|
|
2394
2561
|
const { values } = await (await portOf({ ownerId, chatId })).getChatState(ownerId, chatId);
|
|
2395
2562
|
const run = storedRun(values[FLOW_STATE_KEY]);
|
|
2396
|
-
if (run) await reschedule(ownerId, chatId, run, run.answerId ?? null,
|
|
2563
|
+
if (run) await reschedule(ownerId, chatId, run, run.answerId ?? null, failedAt);
|
|
2397
2564
|
}
|
|
2398
2565
|
async function chatData(chat) {
|
|
2399
2566
|
if (!port && !attachment) return {};
|
|
@@ -2411,7 +2578,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2411
2578
|
if (!run) return;
|
|
2412
2579
|
await reschedule(record.ownerId, record.chatId, migrateRun(run, flow, map, chatTime(run, now())), run.answerId ?? null);
|
|
2413
2580
|
} catch (error) {
|
|
2414
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${record.chatId} \u0442\u0430\u0439\u043C\u0435\u0440 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u043D: ${
|
|
2581
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${record.chatId} \u0442\u0430\u0439\u043C\u0435\u0440 \u043D\u0435 \u043F\u0435\u0440\u0435\u0441\u0447\u0438\u0442\u0430\u043D: ${errorText6(error)}`);
|
|
2415
2582
|
}
|
|
2416
2583
|
}
|
|
2417
2584
|
async function deliverFirstText(ownerId, chatId, messageId, condition, text, guarded2 = false) {
|
|
@@ -2567,10 +2734,18 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2567
2734
|
const { values } = await (await portOf({ ownerId, chatId })).getChatState(ownerId, chatId);
|
|
2568
2735
|
const run = await runOutsideTurn(runtime, { ownerId, chatId }, pinned.flow, values, now(), record);
|
|
2569
2736
|
await store.setDelivery(chatId, null);
|
|
2570
|
-
const
|
|
2571
|
-
|
|
2737
|
+
const at = chatTime(run, now());
|
|
2738
|
+
const result = await finish().end({ ownerId, chatId, pinned, run, reason, ...outcome !== void 0 && { outcome }, at });
|
|
2739
|
+
if (result.ended && await dropDeliveryAfterEnd(chatId, new Date(at).toISOString())) remember(chatId, { ...pinned, ended: true });
|
|
2572
2740
|
return result;
|
|
2573
2741
|
}
|
|
2742
|
+
async function dropDeliveryAfterEnd(chatId, endedAt) {
|
|
2743
|
+
const sameEnd = (record) => record !== null && record.endedAt !== null && Date.parse(record.endedAt) === Date.parse(endedAt);
|
|
2744
|
+
for (let record = await store.getChat(chatId); sameEnd(record); record = await store.getChat(chatId)) {
|
|
2745
|
+
if (await store.swapDelivery(chatId, { expected: record.delivery, next: null, due: null, endedAt })) return true;
|
|
2746
|
+
}
|
|
2747
|
+
return false;
|
|
2748
|
+
}
|
|
2574
2749
|
const CLOSED_FOR_GOOD = /* @__PURE__ */ new Set(["final", "deadline", "blocked"]);
|
|
2575
2750
|
async function reopen({ ownerId, chatId, reason, endedAfter }) {
|
|
2576
2751
|
const pinned = await chatFlow(ownerId, chatId);
|
|
@@ -2612,7 +2787,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2612
2787
|
at: new Date(at)
|
|
2613
2788
|
};
|
|
2614
2789
|
track(
|
|
2615
|
-
Promise.resolve().then(() => onReopen(event)).catch((error) => logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} onReopen \u0443\u043F\u0430\u043B: ${
|
|
2790
|
+
Promise.resolve().then(() => onReopen(event)).catch((error) => logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} onReopen \u0443\u043F\u0430\u043B: ${errorText6(error)}`))
|
|
2616
2791
|
);
|
|
2617
2792
|
}
|
|
2618
2793
|
return { reopened: true };
|
|
@@ -2632,7 +2807,7 @@ function buildAgentFlow(options, modules = {}) {
|
|
|
2632
2807
|
await store.updateChat(chatId, { placement: null }, iso());
|
|
2633
2808
|
} catch (error) {
|
|
2634
2809
|
placedNow.add(chatId);
|
|
2635
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0437\u0430\u043F\u0438\u0441\u044C \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0430 \u043D\u0435 \u0441\u043D\u044F\u0442\u0430: ${
|
|
2810
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${chatId} \u0437\u0430\u043F\u0438\u0441\u044C \u043F\u0435\u0440\u0435\u043D\u043E\u0441\u0430 \u043D\u0435 \u0441\u043D\u044F\u0442\u0430: ${errorText6(error)}`);
|
|
2636
2811
|
}
|
|
2637
2812
|
}
|
|
2638
2813
|
async function readRun(ownerId, chatId) {
|
|
@@ -2742,7 +2917,7 @@ ${rules}`;
|
|
|
2742
2917
|
try {
|
|
2743
2918
|
await store.updateChat(input.chatId, { stateId: run.state }, iso());
|
|
2744
2919
|
} catch (error) {
|
|
2745
|
-
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${input.chatId} \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: ${
|
|
2920
|
+
logError(`[\u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0439] \u0447\u0430\u0442=${input.chatId} \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u0435 \u043D\u0435 \u0437\u0430\u043F\u0438\u0441\u0430\u043D\u043E \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435: ${errorText6(error)}`);
|
|
2746
2921
|
}
|
|
2747
2922
|
await reschedule(input.ownerId, input.chatId, run, input.reason === "flowCommit" ? run.answerId ?? null : input.atMessageId);
|
|
2748
2923
|
})();
|
|
@@ -2755,6 +2930,9 @@ ${rules}`;
|
|
|
2755
2930
|
};
|
|
2756
2931
|
},
|
|
2757
2932
|
attach(target) {
|
|
2933
|
+
if ("portFor" in target && target.deliver && typeof store.swapDelivery !== "function") {
|
|
2934
|
+
throw new TypeError("flow.attach({ deliver }): \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0443 \u0441\u0446\u0435\u043D\u0430\u0440\u0438\u0435\u0432 \u043D\u0443\u0436\u0435\u043D swapDelivery (\u0441 0.81) \u2014 \u0431\u0435\u0437 \u043D\u0435\u0433\u043E \u0434\u043E\u0441\u0442\u0430\u0432\u043A\u0430 \u043D\u0435 \u0437\u0430\u0449\u0438\u0449\u0435\u043D\u0430 \u043E\u0442 \u0432\u0442\u043E\u0440\u043E\u0439 \u043A\u043E\u043F\u0438\u0438");
|
|
2935
|
+
}
|
|
2758
2936
|
if ("portFor" in target) {
|
|
2759
2937
|
attachment = target;
|
|
2760
2938
|
port = void 0;
|
|
@@ -2768,7 +2946,7 @@ ${rules}`;
|
|
|
2768
2946
|
if (signal.wakeAgent) return sendFlowSignal({ runtime, port: await portOf(signal), signal });
|
|
2769
2947
|
await store.addPending(signal.chatId, {
|
|
2770
2948
|
kind: "event",
|
|
2771
|
-
key: `signal:${
|
|
2949
|
+
key: `signal:${randomUUID5()}`,
|
|
2772
2950
|
name: signal.name,
|
|
2773
2951
|
...signal.data !== void 0 && { data: signal.data },
|
|
2774
2952
|
at: iso()
|
|
@@ -2782,6 +2960,7 @@ ${rules}`;
|
|
|
2782
2960
|
resume: (input) => pauses().resume(input),
|
|
2783
2961
|
noteStaffReply: (input) => pauses().noteStaffReply(input),
|
|
2784
2962
|
reportDelivery: (input) => delivery().report(input),
|
|
2963
|
+
resumeDelivery: (input) => delivery().resume(input),
|
|
2785
2964
|
readRun,
|
|
2786
2965
|
revokeConfirmation,
|
|
2787
2966
|
previewMigration: (input) => previewConfirmedMigration(input, { store, confirmations, portFor: portOf }),
|
|
@@ -2834,7 +3013,7 @@ function memoryFlowStore() {
|
|
|
2834
3013
|
const versionKey = (flowId, versionId) => JSON.stringify([flowId, versionId]);
|
|
2835
3014
|
const dayKey = (ownerId, day) => JSON.stringify([ownerId, day]);
|
|
2836
3015
|
const isFree = (row, now) => row.leaseUntil === null || row.leaseUntil < now;
|
|
2837
|
-
const isRunning = (row, now) => row.endedAt === null && (row.pausedUntil === null || row.pausedUntil <= now);
|
|
3016
|
+
const isRunning = (row, now) => (row.endedAt === null || row.dueExitId === FLOW_DELIVERY_EXIT) && (row.pausedUntil === null || row.pausedUntil <= now);
|
|
2838
3017
|
const withChat = (chatId, change) => {
|
|
2839
3018
|
const row = chats.get(chatId);
|
|
2840
3019
|
if (!row) return null;
|
|
@@ -2842,6 +3021,7 @@ function memoryFlowStore() {
|
|
|
2842
3021
|
return toRecord(row);
|
|
2843
3022
|
};
|
|
2844
3023
|
return {
|
|
3024
|
+
guarded: true,
|
|
2845
3025
|
async pinChat(input) {
|
|
2846
3026
|
const existing = chats.get(input.chatId);
|
|
2847
3027
|
if (existing) return toRecord(existing);
|
|
@@ -2903,6 +3083,7 @@ function memoryFlowStore() {
|
|
|
2903
3083
|
async setDue(chatId, due, options) {
|
|
2904
3084
|
const row = chats.get(chatId);
|
|
2905
3085
|
if (!row || options?.unlessDelivery && row.delivery) return;
|
|
3086
|
+
if (options?.leaseOwner !== void 0 && row.leaseOwner !== options.leaseOwner) return;
|
|
2906
3087
|
row.dueAt = due ? normalizeTime(due.at) : null;
|
|
2907
3088
|
row.dueExitId = due?.exitId ?? null;
|
|
2908
3089
|
row.dueBranchHead = due?.branchHead ?? null;
|
|
@@ -2986,7 +3167,8 @@ function memoryFlowStore() {
|
|
|
2986
3167
|
async endChat(chatId, { reason, at }) {
|
|
2987
3168
|
return withChat(chatId, (row) => {
|
|
2988
3169
|
if (row.endedAt !== null) return;
|
|
2989
|
-
Object.assign(row, { endedAt: normalizeTime(at), endReason: reason
|
|
3170
|
+
Object.assign(row, { endedAt: normalizeTime(at), endReason: reason });
|
|
3171
|
+
if (row.delivery === null) Object.assign(row, { dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null });
|
|
2990
3172
|
});
|
|
2991
3173
|
},
|
|
2992
3174
|
async reopenChat(chatId, { endedAt, at }) {
|
|
@@ -3005,7 +3187,18 @@ function memoryFlowStore() {
|
|
|
3005
3187
|
withChat(chatId, (row) => row.dayClosedOn = day);
|
|
3006
3188
|
},
|
|
3007
3189
|
async setDelivery(chatId, delivery) {
|
|
3008
|
-
withChat(chatId, (row) => row.delivery = delivery === null ? null : copy(
|
|
3190
|
+
withChat(chatId, (row) => row.delivery = delivery === null ? null : copy(normalizedDelivery(delivery)));
|
|
3191
|
+
},
|
|
3192
|
+
async swapDelivery(chatId, { expected, next, due, claim, freeAt, endedAt }) {
|
|
3193
|
+
const row = chats.get(chatId);
|
|
3194
|
+
if (!row || !sameDelivery(row.delivery, expected)) return false;
|
|
3195
|
+
if (endedAt !== void 0 && row.endedAt !== (endedAt && normalizeTime(endedAt))) return false;
|
|
3196
|
+
if (claim && !(isFree(row, normalizeTime(claim.now)) || row.leaseOwner === claim.owner || claim.from !== void 0 && row.leaseOwner === claim.from)) return false;
|
|
3197
|
+
if (freeAt !== void 0 && !isFree(row, normalizeTime(freeAt))) return false;
|
|
3198
|
+
row.delivery = next === null ? null : copy(normalizedDelivery(next));
|
|
3199
|
+
if (due !== void 0) Object.assign(row, { dueAt: due ? normalizeTime(due.at) : null, dueExitId: due?.exitId ?? null, dueBranchHead: due?.branchHead ?? null });
|
|
3200
|
+
if (claim) Object.assign(row, { leaseOwner: claim.owner, leaseUntil: new Date(Date.parse(normalizeTime(claim.now)) + claim.leaseMs).toISOString() });
|
|
3201
|
+
return true;
|
|
3009
3202
|
},
|
|
3010
3203
|
async readTimeZone(ownerId) {
|
|
3011
3204
|
return timeZones.get(ownerId) ?? null;
|