@ykurilov/agent-chat-flows 0.80.0 → 0.81.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/dist/index.js CHANGED
@@ -57,7 +57,7 @@ import {
57
57
  transitionReport,
58
58
  withPending,
59
59
  withReopen
60
- } from "./chunk-CZ3EDVYW.js";
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
- pendingAdded
153
- } from "./chunk-DCTRMCGV.js";
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 randomUUID4 } from "crypto";
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 as ChatNotFoundError2, SpendingLimitReachedError } from "@ykurilov/agent-chat-core";
975
+ import { ChatNotFoundError, SpendingLimitReachedError } from "@ykurilov/agent-chat-core";
973
976
 
974
- // src/runtime/flow-delivery.ts
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
- var isoOf = (at) => new Date(at).toISOString();
980
- var hasContent = (answer) => answer.text.trim() !== "" || answer.parts.some((part) => part.type !== "text");
981
- function createFlowDelivery(input) {
982
- const { runtime } = input;
983
- const store = runtime.store;
984
- const portOf = (chat) => {
985
- 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");
986
- return input.port(chat);
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
- async function markUndelivered(ownerId, chatId, messageId) {
989
- try {
990
- await (await portOf({ ownerId, chatId })).markUndelivered({ ownerId, chatId, messageId });
991
- } catch (error) {
992
- if (error instanceof ChatNotFoundError) return;
993
- 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: ${errorText3(error)}`);
994
- }
995
- }
996
- async function blocked(ownerId, chatId) {
997
- await store.setDelivery(chatId, null);
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
- own: () => Boolean(input.deliver()),
1054
- send,
1055
- async afterFirstTurn({ ownerId, chatId, answer, reasons, at, timeZone }) {
1056
- if (!input.deliver()) {
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 isoOf2 = (at) => new Date(at).toISOString();
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: isoOf2(due.at - offset), exitId: due.exitId, branchHead });
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
- if (record.endedAt !== null) return store.setDue(chatId, null);
1182
- if (record.delivery) return store.setDue(chatId, { at: record.delivery.retryAt, exitId: FLOW_DELIVERY_EXIT, branchHead });
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
- let record = leased;
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 store.setDue(chatId, null);
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 ChatNotFoundError2)) throw error;
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 store.setDue(chatId, wake ? dueInput(wake, run.answerId ?? null, offset) : null);
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 store.setDue(chatId, null);
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 store.setDue(chatId, null);
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 store.setDue(chatId, deferred);
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 store.setDue(chatId, following ? dueInput(following, result.messageId ?? null, offset) : null, { unlessDelivery: true });
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
- const outcome = await delivery.afterFirstTurn({ ownerId, chatId, answer: result.answer, reasons, at, timeZone });
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 heartbeat = setInterval(() => {
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
- clearInterval(heartbeat);
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 flow = deps.checkedFlow(loaded);
1437
- if (!flow) 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.`);
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, flow) === "conflict") {
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
- return { flow: found.flow };
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 ChatNotFoundError3 } from "@ykurilov/agent-chat-core";
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 ChatNotFoundError3) return;
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 errorText5 = (error) => error instanceof Error ? error.message : String(error);
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: ${errorText5(error)}`);
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: ${errorText5(error)}`)).finally(() => background.delete(tracked));
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) confirmations.validate(flowId, saved.flow);
2187
- return saved?.flow ?? null;
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
- return { flow: parsed.flow };
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: ${errorText5(error)}`);
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: ${errorText5(error)}`);
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
- onUndelivered: rescheduleUndelivered
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: ${errorText5(error)}`);
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 rescheduleUndelivered({ ownerId, chatId, at }) {
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, at);
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: ${errorText5(error)}`);
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 result = await finish().end({ ownerId, chatId, pinned, run, reason, ...outcome !== void 0 && { outcome }, at: chatTime(run, now()) });
2571
- if (result.ended) remember(chatId, { ...pinned, ended: true });
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: ${errorText5(error)}`))
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: ${errorText5(error)}`);
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: ${errorText5(error)}`);
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:${randomUUID4()}`,
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, dueAt: null, dueExitId: null, dueBranchHead: null, leaseUntil: null, leaseOwner: null });
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({ ...delivery, retryAt: normalizeTime(delivery.retryAt) }));
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;