@fevex/core 0.1.0-alpha.3 → 0.1.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +183 -2
  2. package/dist/agents/index.d.mts +1 -1
  3. package/dist/artifacts/index.d.mts +2 -0
  4. package/dist/artifacts/index.mjs +2 -0
  5. package/dist/artifacts-gUzemET7.mjs +440 -0
  6. package/dist/channels/index.d.mts +2 -2
  7. package/dist/channels/index.mjs +70 -28
  8. package/dist/evals/index.d.mts +4 -4
  9. package/dist/{fevex-Ddyi6z6L.d.mts → fevex-B5NxFKcV.d.mts} +41 -11
  10. package/dist/http/index.d.mts +65 -5
  11. package/dist/http/index.mjs +236 -16
  12. package/dist/{index-Bgcxgy66.d.mts → index--_LdSEuF.d.mts} +31 -10
  13. package/dist/{index-CrOId2px.d.mts → index-BFnWUu7j.d.mts} +1 -1
  14. package/dist/{index-CTIwrVf9.d.mts → index-BPwkgdo3.d.mts} +1 -1
  15. package/dist/{index-DviHtsLo.d.mts → index-Br-zPxZH.d.mts} +2 -2
  16. package/dist/{index-Dx_LotWZ.d.mts → index-CGmHXV31.d.mts} +38 -3
  17. package/dist/{index-CqOjvtQ9.d.mts → index-CKQCNvJz.d.mts} +4 -2
  18. package/dist/{index-dkFyTNh4.d.mts → index-Cb3AB98n.d.mts} +2 -2
  19. package/dist/{index-B4PIkqOU.d.mts → index-D6lnI92z.d.mts} +3 -3
  20. package/dist/{index-Bg9rr8Qe.d.mts → index-Dsulk-n6.d.mts} +3 -3
  21. package/dist/{index-DeXUtyd9.d.mts → index-aayvtf0J.d.mts} +2 -2
  22. package/dist/index.d.mts +13 -13
  23. package/dist/index.mjs +464 -103
  24. package/dist/knowledge/index.d.mts +1 -1
  25. package/dist/model-stream-JQdHDDcj.mjs +131 -0
  26. package/dist/models/index.d.mts +1 -1
  27. package/dist/observability/index.d.mts +1 -1
  28. package/dist/policies/index.d.mts +1 -1
  29. package/dist/{model-stream-u2xMsEaf.mjs → run-support-CmZ6Xo6H.mjs} +41 -130
  30. package/dist/runtime/index.d.mts +2 -2
  31. package/dist/runtime/index.mjs +43 -1
  32. package/dist/sandbox/index.d.mts +1 -1
  33. package/dist/sandbox/index.mjs +42 -17
  34. package/dist/teams/index.d.mts +1 -1
  35. package/dist/testing/index.d.mts +13 -7
  36. package/dist/testing/index.mjs +98 -2
  37. package/dist/tools/index.d.mts +2 -2
  38. package/dist/types-BDVUoAU6.d.mts +559 -0
  39. package/dist/workflows/index.d.mts +1 -1
  40. package/package.json +6 -2
  41. package/dist/types-CuoRx12Q.d.mts +0 -333
package/dist/index.mjs CHANGED
@@ -1,11 +1,13 @@
1
1
  import { defineAgent } from "./agents/index.mjs";
2
- import { ChannelError, handleChannelInput } from "./channels/index.mjs";
2
+ import { a as CatalogedArtifactStore, c as assertArtifactId, i as FileSystemArtifactStore, n as InMemoryArtifactStore, o as ArtifactError, r as InMemoryArtifactCatalog, s as artifactTenantId, t as S3ArtifactStore } from "./artifacts-gUzemET7.mjs";
3
+ import { n as RunPausedError, t as FevexRunError } from "./run-error-Cr17wPGH.mjs";
4
+ import { ChannelError, deliverChannelRun, handleChannelInput } from "./channels/index.mjs";
3
5
  import { InMemoryMemoryStore, createMemoryContextProvider, defineContextProvider, defineSkill } from "./knowledge/index.mjs";
4
6
  import { n as teamAsWorkflow, t as defineTeam } from "./teams-Ctz676G4.mjs";
5
- import { n as RunPausedError, t as FevexRunError } from "./run-error-Cr17wPGH.mjs";
6
- import { InMemoryRunStore, isDurableRunStore, validateListEventsOptions } from "./runtime/index.mjs";
7
+ import { InMemoryRunStore, isDurableRunStore, isRetainableRunStore, validateListEventsOptions, validatePurgeRunsOptions } from "./runtime/index.mjs";
7
8
  import { FEVEX_JSON_SCHEMA_PROFILE_VERSION, IntegrationError, compileFevexJsonSchema, defineConnection, defineTool, toToolSpec, validateFevexJsonSchemaProfile } from "./tools/index.mjs";
8
- import { a as assertTokenBudget, c as remainingOutputTokens, d as serializeValue, f as toJsonValue, i as assertContinuationBudget, l as toErrorMessage, n as abortable, o as cancellationReason, r as addUsage, s as eventUsage, t as readModelStream, u as serializeJsonValue } from "./model-stream-u2xMsEaf.mjs";
9
+ import { _ as serializeValue, a as assertTokenBudget, c as eventUsage, d as remainingDeadlineMs, f as remainingOutputTokens, g as serializeJsonValue, h as toErrorMessage, i as assertContinuationBudget, l as isTimeoutError, m as runSignal, n as abortable, o as cancellationReason, p as resumeDeadlineSignal, r as addUsage, s as deadlineAtFromMs, u as minDeadline, v as toJsonValue } from "./run-support-CmZ6Xo6H.mjs";
10
+ import { t as readModelStream } from "./model-stream-JQdHDDcj.mjs";
9
11
  import "./models/index.mjs";
10
12
  import { definePolicy } from "./policies/index.mjs";
11
13
  import { SandboxError, createLocalSandbox, sandboxResultToJson } from "./sandbox/index.mjs";
@@ -103,6 +105,9 @@ function isRunStore(value) {
103
105
  function isSandbox(value) {
104
106
  return isRecord(value) && typeof value.run === "function";
105
107
  }
108
+ function isArtifactStore(value) {
109
+ return (typeof value === "object" && value !== null || typeof value === "function") && typeof value.put === "function" && typeof value.get === "function";
110
+ }
106
111
  function isJsonObject(value) {
107
112
  return isRecord(value);
108
113
  }
@@ -300,7 +305,21 @@ function createComposition(config) {
300
305
  assertConfiguration(config.runStore === void 0 || isRunStore(config.runStore), "INVALID_CONFIG", "Fevex config \"runStore\" must implement RunStore");
301
306
  assertConfiguration(config.credentialStore === void 0 || typeof config.credentialStore?.resolve === "function", "INVALID_CONFIG", "Fevex config \"credentialStore\" must implement resolve");
302
307
  assertConfiguration(config.sandbox === void 0 || isSandbox(config.sandbox), "INVALID_CONFIG", "Fevex config \"sandbox\" must implement Sandbox");
308
+ assertConfiguration(config.artifacts === void 0 || isArtifactStore(config.artifacts), "INVALID_CONFIG", "Fevex config \"artifacts\" must implement ArtifactStore");
303
309
  assertConfiguration(config.policies === void 0 || Array.isArray(config.policies), "INVALID_CONFIG", "Fevex config \"policies\" must be an array");
310
+ if (config.admission !== void 0) {
311
+ assertConfiguration(isRecord(config.admission), "INVALID_CONFIG", "Fevex config \"admission\" must be an object");
312
+ for (const name of ["maxActiveRuns", "maxActiveRunsPerTenant"]) {
313
+ const value = config.admission[name];
314
+ assertConfiguration(value === void 0 || typeof value === "number" && Number.isSafeInteger(value) && value > 0, "INVALID_CONFIG", `Fevex config "admission.${name}" must be a positive safe integer`);
315
+ }
316
+ assertConfiguration(config.admission.tenantKey === void 0 || typeof config.admission.tenantKey === "function", "INVALID_CONFIG", "Fevex config \"admission.tenantKey\" must be a function");
317
+ assertConfiguration(config.admission.authorize === void 0 || typeof config.admission.authorize === "function", "INVALID_CONFIG", "Fevex config \"admission.authorize\" must be a function");
318
+ }
319
+ if (config.limits !== void 0) {
320
+ assertConfiguration(isRecord(config.limits), "INVALID_CONFIG", "Fevex config \"limits\" must be an object");
321
+ assertConfiguration(config.limits.maxEventsPerRun === void 0 || typeof config.limits.maxEventsPerRun === "number" && Number.isSafeInteger(config.limits.maxEventsPerRun) && config.limits.maxEventsPerRun > 0, "INVALID_CONFIG", "Fevex config \"limits.maxEventsPerRun\" must be a positive safe integer");
322
+ }
304
323
  if (config.observability !== void 0) {
305
324
  assertConfiguration(isRecord(config.observability), "INVALID_CONFIG", "Fevex config \"observability\" must be an object");
306
325
  assertConfiguration(Array.isArray(config.observability.exporters) && config.observability.exporters.every((exporter) => isRecord(exporter) && typeof exporter.export === "function"), "INVALID_CONFIG", "Fevex config \"observability.exporters\" must contain TraceExporters");
@@ -594,8 +613,11 @@ function createComposition(config) {
594
613
  runStore: config.runStore ?? new InMemoryRunStore(),
595
614
  credentialStore: config.credentialStore,
596
615
  sandbox: config.sandbox,
616
+ artifacts: config.artifacts,
597
617
  policies,
598
618
  onEvent: config.onEvent,
619
+ admission: config.admission === void 0 ? void 0 : { ...config.admission },
620
+ limits: config.limits === void 0 ? void 0 : { ...config.limits },
599
621
  observability: config.observability === void 0 ? void 0 : {
600
622
  exporters: [...config.observability.exporters],
601
623
  ...config.observability.calculateCost ? { calculateCost: config.observability.calculateCost } : {},
@@ -732,6 +754,7 @@ const toolErrorPayload = (tool, error) => ({
732
754
  } : {}
733
755
  });
734
756
  const runErrorPayload = (error) => {
757
+ if (error instanceof FevexRunError) return { errorCode: error.code };
735
758
  if (!(error instanceof IntegrationError)) return {};
736
759
  return {
737
760
  errorCode: error.code,
@@ -866,12 +889,12 @@ function addWorkflowBudget(first, second) {
866
889
  * engine — that direction is what keeps the two separable.
867
890
  */
868
891
  function createAgentEngine(composition, core) {
869
- const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, policies } = composition;
870
- const { activeRuns, activeSessions, runtimeOwner, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
871
- const prepareExecution = async (name, request) => {
892
+ const { models, agents, tools, contextProviders, memoryStore, runStore, credentialStore, sandbox, artifacts, policies } = composition;
893
+ const { activeRuns, activeSessions, runtimeOwner, admitStart, markInternalRun, resolveModel, resolveCheckpointModel, notifyObserver, createEvent, emitEvent, releaseExecution, commit, startLease, cancelExecution, authorize } = core;
894
+ const prepareExecutionBody = async (name, request, internal = false) => {
872
895
  const agent = agents.get(name);
873
896
  if (!agent) throw new FevexRunError("AGENT_NOT_FOUND", `Agent "${name}" is not registered`);
874
- const { modelName } = resolveModel(agent, request.model);
897
+ const { modelName, model } = resolveModel(agent, request.model);
875
898
  const now = (/* @__PURE__ */ new Date()).toISOString();
876
899
  let session;
877
900
  const newSession = request.sessionId === void 0;
@@ -887,18 +910,23 @@ function createAgentEngine(composition, core) {
887
910
  session = stored;
888
911
  }
889
912
  if (activeSessions.has(session.id)) throw new FevexRunError("RUN_CONFLICT", `Session "${session.id}" already has an active run`);
890
- activeSessions.add(session.id);
891
913
  const controller = new AbortController();
892
- const signal = request.signal ? AbortSignal.any([controller.signal, request.signal]) : controller.signal;
914
+ const signal = runSignal(controller, request);
915
+ const deadlineAt = deadlineAtFromMs(request.deadlineMs);
916
+ activeSessions.add(session.id);
893
917
  const run = {
894
918
  id: crypto.randomUUID(),
895
919
  sessionId: session.id,
896
920
  agentName: name,
921
+ modelName,
922
+ ...model.metadata === void 0 ? {} : { modelMetadata: structuredClone(model.metadata) },
897
923
  status: "running",
898
924
  revision: 0,
899
925
  createdAt: now,
900
- updatedAt: now
926
+ updatedAt: now,
927
+ ...deadlineAt === void 0 ? {} : { deadlineAt }
901
928
  };
929
+ if (internal) markInternalRun(run.id);
902
930
  const state = {
903
931
  run,
904
932
  session,
@@ -979,6 +1007,14 @@ function createAgentEngine(composition, core) {
979
1007
  activeRuns.set(run.id, state);
980
1008
  return state;
981
1009
  };
1010
+ const prepareExecution = async (name, request, options = {}) => {
1011
+ const release = options.admit === false ? void 0 : await admitStart("agent", name, request);
1012
+ try {
1013
+ return await prepareExecutionBody(name, request, options.admit === false);
1014
+ } finally {
1015
+ release?.();
1016
+ }
1017
+ };
982
1018
  const durableCheckpoint = async (state, model, modelName, messages, inputContent, usage, providerState, step, toolCallCount, seenToolCallIds, pendingTools, pendingIndex) => {
983
1019
  const agent = agents.get(state.run.agentName);
984
1020
  if (agent.model !== void 0 && typeof agent.model !== "string") throw new FevexRunError("RUN_NOT_RESUMABLE", "Durable continuation requires a named model", state.run.id);
@@ -1517,6 +1553,20 @@ function createAgentEngine(composition, core) {
1517
1553
  }
1518
1554
  events.push(started);
1519
1555
  yield started;
1556
+ const createdArtifacts = [];
1557
+ const toolArtifacts = artifacts === void 0 ? void 0 : scopedArtifacts(artifacts, {
1558
+ runId: state.run.id,
1559
+ sessionId: state.session.id,
1560
+ request,
1561
+ emit: async (descriptor) => {
1562
+ createdArtifacts.push(await emit("artifact.created", {
1563
+ descriptor,
1564
+ toolCallId: pending.call.id,
1565
+ toolName: pending.call.name,
1566
+ ...toolSourcePayload(tool)
1567
+ }));
1568
+ }
1569
+ });
1520
1570
  try {
1521
1571
  const raw = await abortable(() => tool.execute(pending.input, {
1522
1572
  runId: state.run.id,
@@ -1525,9 +1575,11 @@ function createAgentEngine(composition, core) {
1525
1575
  idempotencyKey: pending.idempotencyKey,
1526
1576
  getCredential,
1527
1577
  ...scopedSandbox === void 0 ? {} : { sandbox: scopedSandbox },
1578
+ ...toolArtifacts === void 0 ? {} : { artifacts: toolArtifacts },
1528
1579
  context: request.context,
1529
1580
  signal: request.signal
1530
1581
  }), request.signal);
1582
+ for (const event of createdArtifacts.splice(0)) yield event;
1531
1583
  output = toJsonValue(tool.outputSchema === void 0 ? raw : await validateSchema(tool.outputSchema, raw, `Output from tool "${tool.name}" does not match outputSchema`), `Output from tool "${tool.name}" must be JSON-serializable`);
1532
1584
  if (containsSecret(output, secrets)) throw new Error(`Output from tool "${tool.name}" contains a credential`);
1533
1585
  record = {
@@ -1555,6 +1607,7 @@ function createAgentEngine(composition, core) {
1555
1607
  yield completed;
1556
1608
  break;
1557
1609
  } catch (error) {
1610
+ for (const event of createdArtifacts.splice(0)) yield event;
1558
1611
  if (request.signal.aborted) throw error;
1559
1612
  const safeError = redact(toErrorMessage(error), secrets);
1560
1613
  record = {
@@ -1662,7 +1715,7 @@ function createAgentEngine(composition, core) {
1662
1715
  }
1663
1716
  }
1664
1717
  };
1665
- const resumeAgent = async (runId, resolution, waitForCompletion = false, recoveryActor) => {
1718
+ const resumeAgentBody = async (runId, resolution, waitForCompletion = false, recoveryActor) => {
1666
1719
  if (resolution?.type === "timer" || resolution?.type === "event") throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" cannot be resumed`, runId);
1667
1720
  if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "resumeRun requires DurableRunStore", runId);
1668
1721
  const run = await runStore.getRun(runId);
@@ -1670,11 +1723,20 @@ function createAgentEngine(composition, core) {
1670
1723
  if (!run || !checkpoint) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be ${resolution ? "resumed" : "recovered"}`, runId);
1671
1724
  if (checkpoint.version !== 2) throw new FevexRunError("CHECKPOINT_UNSUPPORTED", `Checkpoint for run "${runId}" is unsupported`, runId);
1672
1725
  if (resolution && run.status !== "paused" && run.status !== "running" || !resolution && run.status !== "running") throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" is not ${resolution ? "resumable" : "recoverable"}`, runId);
1726
+ if (run.deadlineAt !== void 0 && Date.parse(run.deadlineAt) <= Date.now()) {
1727
+ if (!await cancelStoredAgent(runId, "timeout")) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" deadline has passed`, runId);
1728
+ return await runStore.getRun(runId);
1729
+ }
1730
+ const deadlineSignal = resumeDeadlineSignal(run.deadlineAt, runId, resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE");
1673
1731
  const agent = agents.get(run.agentName);
1674
1732
  if (!agent) throw new FevexRunError("RUN_DEFINITION_CHANGED", `Agent "${run.agentName}" is unavailable`, runId);
1675
1733
  const checkpointModel = await resolveCheckpointModel(run.agentName, agent, checkpoint);
1676
1734
  if (!checkpointModel) throw new FevexRunError("RUN_DEFINITION_CHANGED", `Definition for agent "${run.agentName}" changed`, runId);
1677
1735
  const { modelName, model } = checkpointModel;
1736
+ if (run.modelName === void 0) {
1737
+ run.modelName = modelName;
1738
+ run.modelMetadata = model.metadata === void 0 ? void 0 : structuredClone(model.metadata);
1739
+ }
1678
1740
  if (checkpoint.providerState !== void 0 && !model.stateCodec) throw new FevexRunError("RUN_NOT_RESUMABLE", "ModelGateway no longer provides its stateCodec", runId);
1679
1741
  const session = await runStore.getSession(run.sessionId);
1680
1742
  if (!session) throw new Error(`Session "${run.sessionId}" does not exist`);
@@ -1689,6 +1751,7 @@ function createAgentEngine(composition, core) {
1689
1751
  if (!await runStore.acquireLease(lease)) throw new FevexRunError("RUN_CONFLICT", `Run "${runId}" is leased`, runId);
1690
1752
  try {
1691
1753
  const controller = new AbortController();
1754
+ const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
1692
1755
  const state = {
1693
1756
  run,
1694
1757
  session,
@@ -1699,7 +1762,7 @@ function createAgentEngine(composition, core) {
1699
1762
  sessionId: session.id,
1700
1763
  context: checkpoint.context,
1701
1764
  limits: checkpoint.limits,
1702
- signal: controller.signal
1765
+ signal
1703
1766
  },
1704
1767
  controller,
1705
1768
  eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
@@ -1868,10 +1931,27 @@ function createAgentEngine(composition, core) {
1868
1931
  throw error;
1869
1932
  }
1870
1933
  };
1871
- const cancelStoredAgent = async (runId) => {
1934
+ const resumeAgent = async (runId, resolution, waitForCompletion = false, recoveryActor, options = {}) => {
1935
+ let release;
1936
+ if (options.admit !== false) {
1937
+ const run = await runStore.getRun(runId);
1938
+ const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(runId) : void 0;
1939
+ if (run && checkpoint && (run.status === "paused" || run.status === "running")) release = await admitStart("agent", run.agentName, {
1940
+ input: "",
1941
+ ...checkpoint.context === void 0 ? {} : { context: checkpoint.context }
1942
+ });
1943
+ }
1944
+ try {
1945
+ if (options.internal) markInternalRun(runId);
1946
+ return await resumeAgentBody(runId, resolution, waitForCompletion, recoveryActor);
1947
+ } finally {
1948
+ release?.();
1949
+ }
1950
+ };
1951
+ const cancelStoredAgent = async (runId, reason = "aborted") => {
1872
1952
  const active = activeRuns.get(runId);
1873
1953
  if (active && !active.request.signal.aborted) {
1874
- active.controller.abort(new DOMException("Run cancelled", "AbortError"));
1954
+ active.controller.abort(new DOMException(reason === "timeout" ? "Run deadline exceeded" : "Run cancelled", reason === "timeout" ? "TimeoutError" : "AbortError"));
1875
1955
  if (!active.advancing) {
1876
1956
  await cancelExecution(active);
1877
1957
  return true;
@@ -1914,8 +1994,8 @@ function createAgentEngine(composition, core) {
1914
1994
  };
1915
1995
  run.status = "cancelled";
1916
1996
  run.pause = void 0;
1917
- run.error = "aborted";
1918
- const cancelled = createEvent(state, "run.cancelled", { reason: "aborted" });
1997
+ run.error = reason;
1998
+ const cancelled = createEvent(state, "run.cancelled", { reason });
1919
1999
  await commit(state, {
1920
2000
  checkpoint: null,
1921
2001
  events: [cancelled]
@@ -1937,6 +2017,37 @@ function createAgentEngine(composition, core) {
1937
2017
  cancelStoredAgent
1938
2018
  };
1939
2019
  }
2020
+ function scopedArtifacts(store, options) {
2021
+ const scopedContext = (value) => {
2022
+ const contextTenant = options.request.context?.attributes?.tenantId;
2023
+ const tenantId = typeof contextTenant === "string" ? contextTenant : value?.tenantId;
2024
+ const actor = options.request.context?.actor;
2025
+ return {
2026
+ ...value,
2027
+ runId: options.runId,
2028
+ sessionId: options.sessionId,
2029
+ ...tenantId === void 0 ? {} : { tenantId },
2030
+ ...actor === void 0 ? {} : { actor },
2031
+ signal: options.request.signal
2032
+ };
2033
+ };
2034
+ return {
2035
+ async put(input, context) {
2036
+ const descriptor = await store.put(input, scopedContext(context));
2037
+ await options.emit(descriptor);
2038
+ return descriptor;
2039
+ },
2040
+ get(artifactId, context) {
2041
+ return store.get(artifactId, scopedContext(context));
2042
+ },
2043
+ ...store.head === void 0 ? {} : { head(artifactId, context) {
2044
+ return store.head(artifactId, scopedContext(context));
2045
+ } },
2046
+ ...store.delete === void 0 ? {} : { delete(artifactId, context) {
2047
+ return store.delete(artifactId, scopedContext(context));
2048
+ } }
2049
+ };
2050
+ }
1940
2051
  //#endregion
1941
2052
  //#region src/internal/observability.ts
1942
2053
  const TERMINAL_TYPES = /* @__PURE__ */ new Set([
@@ -2230,14 +2341,103 @@ function buildRunTrace(run, rawEvents, modelRegistryName, model, options) {
2230
2341
  * the lease table, so a second instance would corrupt event ordering and lease
2231
2342
  * ownership without raising an error.
2232
2343
  */
2233
- function createRunCore({ models, agents, tools, runStore, policies, onEvent, observability }) {
2344
+ function createRunCore({ models, agents, tools, runStore, policies, onEvent, observability, admission, limits }) {
2234
2345
  const activeRuns = /* @__PURE__ */ new Map();
2235
2346
  const activeWorkflowRuns = /* @__PURE__ */ new Map();
2236
2347
  const activeSessions = /* @__PURE__ */ new Set();
2348
+ const internalRuns = /* @__PURE__ */ new Set();
2349
+ const pendingTenantStarts = /* @__PURE__ */ new Map();
2350
+ let pendingStarts = 0;
2237
2351
  const pendingExports = /* @__PURE__ */ new Set();
2238
2352
  const exportFailures = [];
2239
2353
  const exportedRuns = /* @__PURE__ */ new Set();
2240
2354
  const runtimeOwner = crypto.randomUUID();
2355
+ const unboundedEvents = /* @__PURE__ */ new Set([
2356
+ "run.started",
2357
+ "run.recovered",
2358
+ "run.paused",
2359
+ "run.resumed",
2360
+ "elicitation.requested",
2361
+ "elicitation.resolved",
2362
+ "approval.requested",
2363
+ "approval.resolved",
2364
+ "tool.execution_unknown",
2365
+ "run.completed",
2366
+ "run.failed",
2367
+ "run.cancelled",
2368
+ "workflow.run.started",
2369
+ "workflow.run.recovered",
2370
+ "workflow.run.paused",
2371
+ "workflow.run.resumed",
2372
+ "workflow.wait.started",
2373
+ "workflow.wait.completed",
2374
+ "workflow.run.completed",
2375
+ "workflow.run.failed",
2376
+ "workflow.run.cancelled",
2377
+ "team.run.started",
2378
+ "team.run.recovered",
2379
+ "team.run.paused",
2380
+ "team.run.resumed",
2381
+ "team.run.completed",
2382
+ "team.run.failed",
2383
+ "team.run.cancelled"
2384
+ ]);
2385
+ const tenantOf = (context) => {
2386
+ const configured = admission?.tenantKey?.(context);
2387
+ if (configured !== void 0) return configured;
2388
+ const tenantId = context?.attributes?.tenantId;
2389
+ if (typeof tenantId !== "string") return void 0;
2390
+ return tenantId.trim() || void 0;
2391
+ };
2392
+ const countsForAdmission = (runId, state) => !internalRuns.has(runId) && !state.request.signal?.aborted;
2393
+ const activeTopLevelRuns = () => {
2394
+ let count = 0;
2395
+ for (const [runId, state] of activeRuns) if (countsForAdmission(runId, state)) count += 1;
2396
+ for (const [runId, state] of activeWorkflowRuns) if (countsForAdmission(runId, state)) count += 1;
2397
+ return count;
2398
+ };
2399
+ const activeTenantRuns = (tenant) => {
2400
+ let count = 0;
2401
+ for (const [runId, state] of activeRuns) if (countsForAdmission(runId, state) && tenantOf(state.request.context) === tenant) count += 1;
2402
+ for (const [runId, state] of activeWorkflowRuns) if (countsForAdmission(runId, state) && tenantOf(state.request.context) === tenant) count += 1;
2403
+ return count;
2404
+ };
2405
+ const admitStart = async (kind, name, request) => {
2406
+ if (!admission) return () => {};
2407
+ const running = activeTopLevelRuns() + pendingStarts;
2408
+ const tenant = tenantOf(request.context);
2409
+ const tenantRuns = tenant === void 0 ? 0 : activeTenantRuns(tenant) + (pendingTenantStarts.get(tenant) ?? 0);
2410
+ if (admission.maxActiveRuns !== void 0 && running >= admission.maxActiveRuns) throw new FevexRunError("ADMISSION_DENIED", `Runtime reached maxActiveRuns limit of ${admission.maxActiveRuns}`);
2411
+ if (admission.maxActiveRunsPerTenant !== void 0 && tenant !== void 0 && tenantRuns >= admission.maxActiveRunsPerTenant) throw new FevexRunError("ADMISSION_DENIED", `Tenant "${tenant}" reached maxActiveRunsPerTenant limit of ${admission.maxActiveRunsPerTenant}`);
2412
+ pendingStarts += 1;
2413
+ if (tenant !== void 0) pendingTenantStarts.set(tenant, (pendingTenantStarts.get(tenant) ?? 0) + 1);
2414
+ let released = false;
2415
+ const release = () => {
2416
+ if (released) return;
2417
+ released = true;
2418
+ pendingStarts = Math.max(0, pendingStarts - 1);
2419
+ if (tenant === void 0) return;
2420
+ const next = (pendingTenantStarts.get(tenant) ?? 1) - 1;
2421
+ if (next <= 0) pendingTenantStarts.delete(tenant);
2422
+ else pendingTenantStarts.set(tenant, next);
2423
+ };
2424
+ try {
2425
+ if (admission.authorize && !await admission.authorize({
2426
+ kind,
2427
+ name,
2428
+ request,
2429
+ activeRuns: running,
2430
+ activeTenantRuns: tenantRuns
2431
+ })) throw new FevexRunError("ADMISSION_DENIED", "Run admission was denied by the host");
2432
+ } catch (error) {
2433
+ release();
2434
+ throw error;
2435
+ }
2436
+ return release;
2437
+ };
2438
+ const markInternalRun = (runId) => {
2439
+ internalRuns.add(runId);
2440
+ };
2241
2441
  const resolveModel = (agent, requestedModel) => {
2242
2442
  const modelName = requestedModel ?? (typeof agent.model === "string" ? agent.model : "default");
2243
2443
  if (!modelName.trim()) throw new FevexRunError("MODEL_NOT_FOUND", "Model name cannot be empty");
@@ -2271,11 +2471,17 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2271
2471
  if (!run) throw new Error(`Run "${event.runId}" does not exist`);
2272
2472
  if (isCoordinatorRun(run)) return;
2273
2473
  const agentRun = run;
2274
- const agent = agents.get(agentRun.agentName);
2275
- if (!agent) throw new Error(`Agent "${agentRun.agentName}" is not registered`);
2276
- const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(agentRun.id) : void 0;
2277
- const { modelName: modelRegistryName, model: gateway } = resolveModel(agent, checkpoint?.modelName);
2278
- const trace = buildRunTrace(agentRun, await runStore.listEvents(agentRun.id), modelRegistryName, gateway?.metadata, observability);
2474
+ let modelRegistryName = agentRun.modelName;
2475
+ let modelMetadata = agentRun.modelMetadata;
2476
+ if (modelRegistryName === void 0) {
2477
+ const agent = agents.get(agentRun.agentName);
2478
+ if (!agent) throw new Error(`Agent "${agentRun.agentName}" is not registered`);
2479
+ const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(agentRun.id) : void 0;
2480
+ const resolved = resolveModel(agent, checkpoint?.modelName);
2481
+ modelRegistryName = resolved.modelName;
2482
+ modelMetadata = resolved.model.metadata;
2483
+ }
2484
+ const trace = buildRunTrace(agentRun, await runStore.listEvents(agentRun.id), modelRegistryName, modelMetadata, observability);
2279
2485
  const failures = (await Promise.allSettled(observability.exporters.map((exporter) => Promise.resolve().then(() => exporter.export(trace))))).filter((result) => result.status === "rejected").map(({ reason }) => reason);
2280
2486
  if (failures.length) throw new AggregateError(failures, "Trace export failed");
2281
2487
  })().catch((error) => {
@@ -2290,14 +2496,17 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2290
2496
  Promise.resolve(onEvent(event)).catch(() => {});
2291
2497
  } catch {}
2292
2498
  };
2293
- const createEvent = (state, type, payload) => ({
2294
- id: crypto.randomUUID(),
2295
- sequence: state.eventSequence += 1,
2296
- type,
2297
- runId: state.run.id,
2298
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2299
- ...payload === void 0 ? {} : { payload }
2300
- });
2499
+ const createEvent = (state, type, payload) => {
2500
+ if (limits?.maxEventsPerRun !== void 0 && !unboundedEvents.has(type) && state.eventSequence >= limits.maxEventsPerRun) throw new FevexRunError("EVENT_LIMIT_EXCEEDED", `Run exceeded maxEventsPerRun limit of ${limits.maxEventsPerRun}`, state.run.id);
2501
+ return {
2502
+ id: crypto.randomUUID(),
2503
+ sequence: state.eventSequence += 1,
2504
+ type,
2505
+ runId: state.run.id,
2506
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2507
+ ...payload === void 0 ? {} : { payload }
2508
+ };
2509
+ };
2301
2510
  const createCoordinatorEvent = (state, workflowType, workflowPayload, teamType, teamPayload = workflowPayload) => createEvent(state, isTeamRun(state.run) ? teamType : workflowType, isTeamRun(state.run) ? teamPayload : workflowPayload);
2302
2511
  const emitEvent = async (state, type, payload) => {
2303
2512
  const event = createEvent(state, type, payload);
@@ -2311,6 +2520,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2311
2520
  const releaseExecution = async (state) => {
2312
2521
  activeRuns.delete(state.run.id);
2313
2522
  activeSessions.delete(state.session.id);
2523
+ internalRuns.delete(state.run.id);
2314
2524
  if (state.leaseTimer) clearInterval(state.leaseTimer);
2315
2525
  if (state.leaseExpiryTimer) clearTimeout(state.leaseExpiryTimer);
2316
2526
  state.leaseTimer = void 0;
@@ -2399,6 +2609,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2399
2609
  const releaseWorkflowExecution = async (state) => {
2400
2610
  activeWorkflowRuns.delete(state.run.id);
2401
2611
  activeSessions.delete(state.session.id);
2612
+ internalRuns.delete(state.run.id);
2402
2613
  if (state.leaseTimer) clearInterval(state.leaseTimer);
2403
2614
  if (state.leaseExpiryTimer) clearTimeout(state.leaseExpiryTimer);
2404
2615
  state.leaseTimer = void 0;
@@ -2440,6 +2651,8 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2440
2651
  pendingExports,
2441
2652
  exportFailures,
2442
2653
  runtimeOwner,
2654
+ admitStart,
2655
+ markInternalRun,
2443
2656
  resolveModel,
2444
2657
  resolveCheckpointModel,
2445
2658
  notifyObserver,
@@ -2467,7 +2680,7 @@ function createRunCore({ models, agents, tools, runStore, policies, onEvent, obs
2467
2680
  */
2468
2681
  function createWorkflowEngine(composition, core, agentEngine) {
2469
2682
  const { models, agents, workflows, teams, runStore } = composition;
2470
- const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
2683
+ const { activeRuns, activeWorkflowRuns, activeSessions, runtimeOwner, admitStart, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, releaseWorkflowExecution, commitWorkflow } = core;
2471
2684
  const { prepareExecution, executeAgent, nextEvent, resumeAgent, cancelStoredAgent } = agentEngine;
2472
2685
  const cancelWorkflowExecution = async (state, reason = cancellationReason(state.request.signal)) => {
2473
2686
  if (state.leaseLost) {
@@ -2476,7 +2689,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
2476
2689
  }
2477
2690
  if (state.run.status === "completed" || state.run.status === "failed" || state.run.status === "cancelled") return;
2478
2691
  const childRunIds = Object.values(state.checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
2479
- for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId)) throw new FevexRunError("RUN_CONFLICT", `Workflow child "${childRunId}" could not be cancelled`, state.run.id);
2692
+ for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId, reason === "timeout" ? "timeout" : "aborted")) throw new FevexRunError("RUN_CONFLICT", `Workflow child "${childRunId}" could not be cancelled`, state.run.id);
2480
2693
  state.run.status = "cancelled";
2481
2694
  state.run.pause = void 0;
2482
2695
  state.run.error = reason;
@@ -2489,7 +2702,76 @@ function createWorkflowEngine(composition, core, agentEngine) {
2489
2702
  await releaseWorkflowExecution(state);
2490
2703
  return event;
2491
2704
  };
2492
- const prepareWorkflowExecution = async (name, request, kind = "workflow") => {
2705
+ const cancelStoredWorkflow = async (runId, reason = "aborted") => {
2706
+ const active = activeWorkflowRuns.get(runId);
2707
+ if (active && !active.request.signal.aborted) {
2708
+ active.controller.abort(new DOMException(reason === "timeout" ? "Run deadline exceeded" : "Run cancelled", reason === "timeout" ? "TimeoutError" : "AbortError"));
2709
+ if (!active.advancing) {
2710
+ await cancelWorkflowExecution(active);
2711
+ return true;
2712
+ }
2713
+ for (let attempt = 0; attempt < 100; attempt += 1) {
2714
+ const current = await runStore.getRun(runId);
2715
+ if (current?.status === "cancelled" || current?.status === "completed" || current?.status === "failed") return true;
2716
+ await new Promise((resolve) => setTimeout(resolve, 10));
2717
+ }
2718
+ return false;
2719
+ }
2720
+ if (!isDurableRunStore(runStore)) return false;
2721
+ const run = await runStore.getRun(runId);
2722
+ if (!run) return false;
2723
+ if (run.status === "completed" || run.status === "failed" || run.status === "cancelled") return true;
2724
+ const session = await runStore.getSession(run.sessionId);
2725
+ const checkpoint = await runStore.getCheckpoint(runId);
2726
+ if (!session || !checkpoint) return false;
2727
+ const ownerId = `${runtimeOwner}:${crypto.randomUUID()}`;
2728
+ const lease = {
2729
+ generation: 0,
2730
+ runId,
2731
+ ownerId,
2732
+ expiresAt: new Date(Date.now() + LEASE_MS).toISOString()
2733
+ };
2734
+ if (!await runStore.acquireLease(lease)) return false;
2735
+ try {
2736
+ const childRunIds = Object.values(checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
2737
+ for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId, reason)) return false;
2738
+ const state = {
2739
+ run,
2740
+ session,
2741
+ request: {
2742
+ input: checkpoint.input,
2743
+ ...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
2744
+ ...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
2745
+ sessionId: session.id,
2746
+ context: checkpoint.context,
2747
+ limits: checkpoint.limits,
2748
+ signal: new AbortController().signal
2749
+ },
2750
+ controller: new AbortController(),
2751
+ eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
2752
+ checkpoint,
2753
+ advancing: false,
2754
+ lease,
2755
+ commitQueue: Promise.resolve()
2756
+ };
2757
+ run.status = "cancelled";
2758
+ run.pause = void 0;
2759
+ run.error = reason;
2760
+ run.usage = checkpoint.budget?.usage;
2761
+ const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason }, "team.run.cancelled");
2762
+ await commit(state, {
2763
+ checkpoint: null,
2764
+ events: [cancelled]
2765
+ });
2766
+ return true;
2767
+ } catch (error) {
2768
+ if (error instanceof FevexRunError && error.code === "RUN_CONFLICT" && (await runStore.getRun(runId))?.status !== "paused") return false;
2769
+ throw error;
2770
+ } finally {
2771
+ await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
2772
+ }
2773
+ };
2774
+ const prepareWorkflowExecutionBody = async (name, request, kind = "workflow") => {
2493
2775
  const workflow = workflows.get(name);
2494
2776
  const team = kind === "team" ? teams.get(name) : void 0;
2495
2777
  if (!workflow || kind === "team" && !team || kind === "workflow" && teams.has(name)) throw new FevexRunError(kind === "team" ? "TEAM_NOT_FOUND" : "WORKFLOW_NOT_FOUND", `${kind === "team" ? "Team" : "Workflow"} "${name}" is not registered`);
@@ -2508,9 +2790,9 @@ function createWorkflowEngine(composition, core, agentEngine) {
2508
2790
  session = stored;
2509
2791
  }
2510
2792
  if (activeSessions.has(session.id)) throw new FevexRunError("RUN_CONFLICT", `Session "${session.id}" already has an active run`);
2511
- activeSessions.add(session.id);
2512
2793
  const controller = new AbortController();
2513
- const signal = request.signal ? AbortSignal.any([controller.signal, request.signal]) : controller.signal;
2794
+ const signal = runSignal(controller, request);
2795
+ const deadlineAt = deadlineAtFromMs(request.deadlineMs);
2514
2796
  const input = toJsonValue(workflow.inputSchema === void 0 ? request.input : await abortable(() => validateSchema(workflow.inputSchema, request.input, `Input for ${kind} "${name}" does not match inputSchema`), signal), `${kind === "team" ? "Team" : "Workflow"} input must be a string or JSON-serializable value`);
2515
2797
  const id = crypto.randomUUID();
2516
2798
  const run = kind === "team" ? {
@@ -2521,7 +2803,8 @@ function createWorkflowEngine(composition, core, agentEngine) {
2521
2803
  status: "running",
2522
2804
  revision: 0,
2523
2805
  createdAt: now,
2524
- updatedAt: now
2806
+ updatedAt: now,
2807
+ ...deadlineAt === void 0 ? {} : { deadlineAt }
2525
2808
  } : {
2526
2809
  kind: "workflow",
2527
2810
  id,
@@ -2530,7 +2813,8 @@ function createWorkflowEngine(composition, core, agentEngine) {
2530
2813
  status: "running",
2531
2814
  revision: 0,
2532
2815
  createdAt: now,
2533
- updatedAt: now
2816
+ updatedAt: now,
2817
+ ...deadlineAt === void 0 ? {} : { deadlineAt }
2534
2818
  };
2535
2819
  const checkpoint = kind === "team" ? {
2536
2820
  version: 2,
@@ -2557,6 +2841,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
2557
2841
  steps: {},
2558
2842
  limits: combineLimits(workflow.limits, request.limits)
2559
2843
  };
2844
+ activeSessions.add(session.id);
2560
2845
  const state = {
2561
2846
  run,
2562
2847
  session,
@@ -2598,6 +2883,14 @@ function createWorkflowEngine(composition, core, agentEngine) {
2598
2883
  activeWorkflowRuns.set(run.id, state);
2599
2884
  return state;
2600
2885
  };
2886
+ const prepareWorkflowExecution = async (name, request, kind = "workflow") => {
2887
+ const release = await admitStart(kind, name, request);
2888
+ try {
2889
+ return await prepareWorkflowExecutionBody(name, request, kind);
2890
+ } finally {
2891
+ release();
2892
+ }
2893
+ };
2601
2894
  async function* executeWorkflow(name, state) {
2602
2895
  const workflow = workflows.get(name);
2603
2896
  const events = [];
@@ -2759,7 +3052,10 @@ function createWorkflowEngine(composition, core, agentEngine) {
2759
3052
  if (!child) throw new Error(`Child run "${existing.childRunId}" does not exist`);
2760
3053
  if (child.status === "running") {
2761
3054
  if (!state.recoveryActor) throw new FevexRunError("RUN_NOT_RECOVERABLE", `Child run "${child.id}" is still running`, state.run.id);
2762
- child = await resumeAgent(child.id, void 0, true, state.recoveryActor);
3055
+ child = await resumeAgent(child.id, void 0, true, state.recoveryActor, {
3056
+ admit: false,
3057
+ internal: true
3058
+ });
2763
3059
  }
2764
3060
  if (child.status === "completed") return completeAgentStep(stepId, existing.agentName, existing.childRunId, toJsonValue(child.output, "Child agent output must be JSON-serializable"), options);
2765
3061
  if (child.status === "paused" && child.pause) return pauseForChild(stepId, new RunPausedError(child.id, child.pause));
@@ -2770,12 +3066,13 @@ function createWorkflowEngine(composition, core, agentEngine) {
2770
3066
  const childSignal = request.signal ? AbortSignal.any([state.request.signal, request.signal]) : state.request.signal;
2771
3067
  const childState = await prepareExecution(agentName, {
2772
3068
  ...request,
3069
+ deadlineMs: minDeadline(remainingDeadlineMs(state.run.deadlineAt, state.run.id), request.deadlineMs),
2773
3070
  model: request.model ?? state.request.model,
2774
3071
  reasoning: request.reasoning ?? state.request.reasoning,
2775
3072
  context: mergeExecutionContext(state.request.context, request.context),
2776
3073
  limits: combineLimits(request.limits, remainingWorkflowLimits(state.checkpoint.limits, state.checkpoint.budget)),
2777
3074
  signal: childSignal
2778
- });
3075
+ }, { admit: false });
2779
3076
  const action = options?.metadata?.teamAction === "handoff" ? "handoff" : "delegate";
2780
3077
  const lifecycleEvents = [];
2781
3078
  if (isTeamRun(state.run) && action === "handoff") lifecycleEvents.push(createEvent(state, "team.handoff.created", {
@@ -3109,8 +3406,10 @@ function createWorkflowEngine(composition, core, agentEngine) {
3109
3406
  };
3110
3407
  } catch (error) {
3111
3408
  let thrown = error;
3112
- if (state.request.signal.aborted) try {
3113
- const event = await cancelWorkflowExecution(state);
3409
+ const parentDeadline = state.run.deadlineAt === void 0 ? void 0 : Date.parse(state.run.deadlineAt);
3410
+ const parentTimedOut = isTimeoutError(error) && parentDeadline !== void 0 && Date.now() >= parentDeadline - 10;
3411
+ if (state.request.signal.aborted || parentTimedOut) try {
3412
+ const event = await cancelWorkflowExecution(state, parentTimedOut ? "timeout" : void 0);
3114
3413
  if (event) {
3115
3414
  events.push(event);
3116
3415
  yield event;
@@ -3172,13 +3471,18 @@ function createWorkflowEngine(composition, core, agentEngine) {
3172
3471
  }
3173
3472
  }
3174
3473
  };
3175
- const resumeWorkflow = async (runId, resolution, recoveryActor) => {
3474
+ const resumeWorkflowBody = async (runId, resolution, recoveryActor) => {
3176
3475
  if (!isDurableRunStore(runStore)) throw new FevexRunError("DURABLE_STORE_REQUIRED", "resumeRun requires DurableRunStore", runId);
3177
3476
  const run = await runStore.getRun(runId);
3178
3477
  const checkpoint = await runStore.getCheckpoint(runId);
3179
3478
  if (!run || !checkpoint) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" cannot be ${resolution ? "resumed" : "recovered"}`, runId);
3180
3479
  if (checkpoint.version !== 2) throw new FevexRunError("CHECKPOINT_UNSUPPORTED", `Checkpoint for run "${runId}" is unsupported`, runId);
3181
3480
  if (resolution && run.status !== "paused" && run.status !== "running" || !resolution && run.status !== "running") throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" is not ${resolution ? "resumable" : "recoverable"}`, runId);
3481
+ if (run.deadlineAt !== void 0 && Date.parse(run.deadlineAt) <= Date.now()) {
3482
+ if (!await cancelStoredWorkflow(runId, "timeout")) throw new FevexRunError(resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE", `Run "${runId}" deadline has passed`, runId);
3483
+ return await runStore.getRun(runId);
3484
+ }
3485
+ const deadlineSignal = resumeDeadlineSignal(run.deadlineAt, runId, resolution ? "RUN_NOT_RESUMABLE" : "RUN_NOT_RECOVERABLE");
3182
3486
  const name = coordinatorName(run);
3183
3487
  const workflow = workflows.get(name);
3184
3488
  const team = isTeamRun(run) ? teams.get(name) : void 0;
@@ -3201,6 +3505,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3201
3505
  if (!resolution) {
3202
3506
  if (!recoveryActor?.id.trim()) throw new FevexRunError("RUN_NOT_RECOVERABLE", "Recovery actor is required", runId);
3203
3507
  const controller = new AbortController();
3508
+ const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
3204
3509
  const state = {
3205
3510
  run,
3206
3511
  session,
@@ -3211,7 +3516,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3211
3516
  sessionId: session.id,
3212
3517
  context: checkpoint.context,
3213
3518
  limits: checkpoint.limits,
3214
- signal: controller.signal
3519
+ signal
3215
3520
  },
3216
3521
  controller,
3217
3522
  eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
@@ -3254,6 +3559,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3254
3559
  if (validatedPayload !== void 0) eventPayload = toJsonValue(validatedPayload, "Workflow event payload must be JSON-serializable");
3255
3560
  }
3256
3561
  const controller = new AbortController();
3562
+ const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
3257
3563
  const state = {
3258
3564
  run,
3259
3565
  session,
@@ -3264,7 +3570,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3264
3570
  sessionId: session.id,
3265
3571
  context: checkpoint.context,
3266
3572
  limits: checkpoint.limits,
3267
- signal: controller.signal
3573
+ signal
3268
3574
  },
3269
3575
  controller,
3270
3576
  eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
@@ -3302,8 +3608,93 @@ function createWorkflowEngine(composition, core, agentEngine) {
3302
3608
  if (resolution.type === "timer" || resolution.type === "event") throw new FevexRunError("RUN_NOT_RESUMABLE", `Run "${runId}" cannot be resumed`, runId);
3303
3609
  const childRunId = workflowPause.type === "workflow_children" ? workflowPause.children.find(({ childPause }) => pauseMatchesResolution(childPause, resolution))?.childRunId : workflowPause.childRunId;
3304
3610
  if (!childRunId) throw new FevexRunError("RUN_NOT_RESUMABLE", "Resolution does not match a paused child run", runId);
3305
- const childRun = await resumeAgent(childRunId, resolution, true);
3611
+ let childRun;
3612
+ try {
3613
+ childRun = await resumeAgent(childRunId, resolution, true, void 0, {
3614
+ admit: false,
3615
+ internal: true
3616
+ });
3617
+ } catch (error) {
3618
+ const parentDeadline = run.deadlineAt === void 0 ? void 0 : Date.parse(run.deadlineAt);
3619
+ const child = await runStore.getRun(childRunId);
3620
+ const childDeadline = child?.deadlineAt === void 0 ? void 0 : Date.parse(child.deadlineAt);
3621
+ if (isTimeoutError(error) && parentDeadline !== void 0 && childDeadline !== void 0 && Math.abs(childDeadline - parentDeadline) <= 5) {
3622
+ const pausedChildIds = workflowPause.type === "workflow_children" ? workflowPause.children.map(({ childRunId }) => childRunId) : [workflowPause.childRunId];
3623
+ for (const pausedChildId of pausedChildIds) if (!await cancelStoredAgent(pausedChildId, "timeout")) throw error;
3624
+ const cancelController = new AbortController();
3625
+ const cancelState = {
3626
+ run,
3627
+ session,
3628
+ request: {
3629
+ input: checkpoint.input,
3630
+ ...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
3631
+ ...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
3632
+ sessionId: session.id,
3633
+ context: checkpoint.context,
3634
+ limits: checkpoint.limits,
3635
+ signal: cancelController.signal
3636
+ },
3637
+ controller: cancelController,
3638
+ eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
3639
+ checkpoint,
3640
+ advancing: false,
3641
+ lease,
3642
+ commitQueue: Promise.resolve()
3643
+ };
3644
+ run.status = "cancelled";
3645
+ run.pause = void 0;
3646
+ run.error = "timeout";
3647
+ run.usage = checkpoint.budget?.usage;
3648
+ const cancelled = createCoordinatorEvent(cancelState, "workflow.run.cancelled", { reason: "timeout" }, "team.run.cancelled");
3649
+ await commit(cancelState, {
3650
+ checkpoint: null,
3651
+ events: [cancelled]
3652
+ });
3653
+ await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
3654
+ return structuredClone(run);
3655
+ }
3656
+ if (isTimeoutError(error)) {
3657
+ const pausedChildIds = workflowPause.type === "workflow_children" ? workflowPause.children.map(({ childRunId }) => childRunId) : [workflowPause.childRunId];
3658
+ for (const pausedChildId of pausedChildIds) if (!await cancelStoredAgent(pausedChildId, "aborted")) throw error;
3659
+ const failController = new AbortController();
3660
+ const failState = {
3661
+ run,
3662
+ session,
3663
+ request: {
3664
+ input: checkpoint.input,
3665
+ ...checkpoint.model === void 0 ? {} : { model: checkpoint.model },
3666
+ ...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
3667
+ sessionId: session.id,
3668
+ context: checkpoint.context,
3669
+ limits: checkpoint.limits,
3670
+ signal: failController.signal
3671
+ },
3672
+ controller: failController,
3673
+ eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
3674
+ checkpoint,
3675
+ advancing: false,
3676
+ lease,
3677
+ commitQueue: Promise.resolve()
3678
+ };
3679
+ run.status = "failed";
3680
+ run.pause = void 0;
3681
+ run.error = toErrorMessage(error);
3682
+ run.usage = checkpoint.budget?.usage;
3683
+ const failed = createCoordinatorEvent(failState, "workflow.run.failed", {
3684
+ error: toErrorMessage(error),
3685
+ ...runErrorPayload(error)
3686
+ }, "team.run.failed");
3687
+ await commit(failState, {
3688
+ checkpoint: null,
3689
+ events: [failed]
3690
+ });
3691
+ await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
3692
+ return structuredClone(run);
3693
+ }
3694
+ throw error;
3695
+ }
3306
3696
  const controller = new AbortController();
3697
+ const signal = deadlineSignal ? AbortSignal.any([controller.signal, deadlineSignal]) : controller.signal;
3307
3698
  const state = {
3308
3699
  run,
3309
3700
  session,
@@ -3314,7 +3705,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3314
3705
  sessionId: session.id,
3315
3706
  context: checkpoint.context,
3316
3707
  limits: checkpoint.limits,
3317
- signal: controller.signal
3708
+ signal
3318
3709
  },
3319
3710
  controller,
3320
3711
  eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
@@ -3327,7 +3718,7 @@ function createWorkflowEngine(composition, core, agentEngine) {
3327
3718
  run.status = "cancelled";
3328
3719
  run.pause = void 0;
3329
3720
  run.error = childRun.error ?? "aborted";
3330
- const reason = childRun.error === "approval_rejected" ? "approval_rejected" : "aborted";
3721
+ const reason = childRun.error === "approval_rejected" ? "approval_rejected" : childRun.error === "timeout" ? "timeout" : "aborted";
3331
3722
  const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason }, "team.run.cancelled");
3332
3723
  await commit(state, {
3333
3724
  checkpoint: null,
@@ -3353,13 +3744,30 @@ function createWorkflowEngine(composition, core, agentEngine) {
3353
3744
  throw error;
3354
3745
  }
3355
3746
  };
3747
+ const resumeWorkflow = async (runId, resolution, recoveryActor, options = {}) => {
3748
+ let release;
3749
+ if (options.admit !== false) {
3750
+ const run = await runStore.getRun(runId);
3751
+ const checkpoint = isDurableRunStore(runStore) ? await runStore.getCheckpoint(runId) : void 0;
3752
+ if (run && checkpoint && (run.status === "paused" || run.status === "running")) release = await admitStart(isTeamRun(run) ? "team" : "workflow", coordinatorName(run), {
3753
+ input: "",
3754
+ ...checkpoint.context === void 0 ? {} : { context: checkpoint.context }
3755
+ });
3756
+ }
3757
+ try {
3758
+ return await resumeWorkflowBody(runId, resolution, recoveryActor);
3759
+ } finally {
3760
+ release?.();
3761
+ }
3762
+ };
3356
3763
  return {
3357
3764
  prepareWorkflowExecution,
3358
3765
  executeWorkflow,
3359
3766
  nextWorkflowEvent,
3360
3767
  drainWorkflowExecution,
3361
3768
  resumeWorkflow,
3362
- cancelWorkflowExecution
3769
+ cancelWorkflowExecution,
3770
+ cancelStoredWorkflow
3363
3771
  };
3364
3772
  }
3365
3773
  //#endregion
@@ -3370,7 +3778,7 @@ function createRuntime(composition) {
3370
3778
  const { activeRuns, activeWorkflowRuns, activeSessions, pendingExports, exportFailures, runtimeOwner, createEvent, createCoordinatorEvent, notifyObserver, commit, startLease, cancelExecution, releaseWorkflowExecution, commitWorkflow } = core;
3371
3779
  const agentEngine = createAgentEngine(composition, core);
3372
3780
  const { prepareExecution, executeAgent, nextEvent, drainExecution, resumeAgent, cancelStoredAgent } = agentEngine;
3373
- const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution } = createWorkflowEngine(composition, core, agentEngine);
3781
+ const { prepareWorkflowExecution, executeWorkflow, nextWorkflowEvent, drainWorkflowExecution, resumeWorkflow, cancelWorkflowExecution, cancelStoredWorkflow } = createWorkflowEngine(composition, core, agentEngine);
3374
3782
  const resumeRun = async (runId, resolution) => {
3375
3783
  const run = await runStore.getRun(runId);
3376
3784
  if (run && isCoordinatorRun(run)) return resumeWorkflow(runId, resolution);
@@ -3487,58 +3895,7 @@ function createRuntime(composition) {
3487
3895
  }
3488
3896
  const run = await runStore.getRun(runId);
3489
3897
  if (!run || run.status !== "paused" && run.status !== "running") return false;
3490
- if (!isDurableRunStore(runStore)) return false;
3491
- const session = await runStore.getSession(run.sessionId);
3492
- if (!session) return false;
3493
- if (isCoordinatorRun(run)) {
3494
- const checkpoint = await runStore.getCheckpoint(runId);
3495
- if (!checkpoint) return false;
3496
- const ownerId = `${runtimeOwner}:${crypto.randomUUID()}`;
3497
- const lease = {
3498
- generation: 0,
3499
- runId,
3500
- ownerId,
3501
- expiresAt: new Date(Date.now() + LEASE_MS).toISOString()
3502
- };
3503
- if (!await runStore.acquireLease(lease)) return false;
3504
- try {
3505
- const childRunIds = Object.values(checkpoint.steps).filter((record) => record.type === "agent" && record.status === "running").map(({ childRunId }) => childRunId);
3506
- for (const childRunId of childRunIds) if (!await cancelStoredAgent(childRunId)) return false;
3507
- const state = {
3508
- run,
3509
- session,
3510
- request: {
3511
- input: checkpoint.input,
3512
- ...checkpoint.reasoning === void 0 ? {} : { reasoning: checkpoint.reasoning },
3513
- sessionId: session.id,
3514
- context: checkpoint.context,
3515
- limits: checkpoint.limits,
3516
- signal: new AbortController().signal
3517
- },
3518
- controller: new AbortController(),
3519
- eventSequence: (await runStore.listEvents(runId)).at(-1)?.sequence ?? 0,
3520
- checkpoint,
3521
- advancing: false,
3522
- lease,
3523
- commitQueue: Promise.resolve()
3524
- };
3525
- run.status = "cancelled";
3526
- run.pause = void 0;
3527
- run.error = "aborted";
3528
- run.usage = checkpoint.budget?.usage;
3529
- const cancelled = createCoordinatorEvent(state, "workflow.run.cancelled", { reason: "aborted" }, "team.run.cancelled");
3530
- await commit(state, {
3531
- checkpoint: null,
3532
- events: [cancelled]
3533
- });
3534
- return true;
3535
- } catch (error) {
3536
- if (error instanceof FevexRunError && error.code === "RUN_CONFLICT" && (await runStore.getRun(runId))?.status !== "paused") return false;
3537
- throw error;
3538
- } finally {
3539
- await runStore.releaseLease(runId, ownerId, lease.generation).catch(() => {});
3540
- }
3541
- }
3898
+ if (isCoordinatorRun(run)) return cancelStoredWorkflow(runId);
3542
3899
  return cancelStoredAgent(runId);
3543
3900
  },
3544
3901
  resumeRun(runId, resolution) {
@@ -3547,6 +3904,10 @@ function createRuntime(composition) {
3547
3904
  recoverRun(runId, options) {
3548
3905
  return recoverRun(runId, options.actor);
3549
3906
  },
3907
+ async purgeRuns(options) {
3908
+ if (!isRetainableRunStore(runStore)) throw new FevexRunError("RETENTION_UNSUPPORTED", "The configured run store does not support purgeRuns");
3909
+ return runStore.purgeRuns(options);
3910
+ },
3550
3911
  async compactSession(sessionId, summary) {
3551
3912
  if (typeof summary !== "string" || !summary.trim()) throw new Error("Session summary must be a non-empty string");
3552
3913
  if (activeSessions.has(sessionId)) throw new Error(`Session "${sessionId}" has an active run`);
@@ -3581,4 +3942,4 @@ function createFevex(config) {
3581
3942
  return createRuntime(createComposition(config));
3582
3943
  }
3583
3944
  //#endregion
3584
- export { ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, InMemoryMemoryStore, InMemoryRunStore, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, SCHEMA_NOT_TRANSPORTABLE, SandboxError, compileFevexJsonSchema, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, handleChannelInput, isDurableRunStore, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions };
3945
+ export { ArtifactError, CatalogedArtifactStore, ChannelError, FEVEX_JSON_SCHEMA_PROFILE_VERSION, FevexConfigurationError, FevexRunError, FileSystemArtifactStore, InMemoryArtifactCatalog, InMemoryArtifactStore, InMemoryMemoryStore, InMemoryRunStore, IntegrationError, PROVIDER_REASONING_UNSUPPORTED, PROVIDER_SCHEMA_UNSUPPORTED, RunPausedError, S3ArtifactStore, SCHEMA_NOT_TRANSPORTABLE, SandboxError, artifactTenantId, assertArtifactId, compileFevexJsonSchema, createFevex, createLocalSandbox, createMemoryContextProvider, defineAgent, defineConnection, defineContextProvider, definePolicy, defineSkill, defineTeam, defineTool, defineWorkflow, deliverChannelRun, handleChannelInput, isDurableRunStore, isRetainableRunStore, sandboxResultToJson, toToolSpec, validateFevexJsonSchemaProfile, validateListEventsOptions, validatePurgeRunsOptions };