skedyul 1.5.13 → 1.6.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.
@@ -410,6 +410,8 @@ var CRMPageSchemaZ = z3.object({
410
410
  icon: z3.string().optional(),
411
411
  modelHandle: z3.string(),
412
412
  parentPath: z3.string().optional(),
413
+ isDefault: z3.boolean().optional(),
414
+ visibility: z3.enum(["team", "private"]).optional(),
413
415
  baseQuery: z3.unknown().optional(),
414
416
  blocks: z3.array(CRMBlockSchemaZ).optional()
415
417
  });
@@ -1285,7 +1287,8 @@ var ModelDefinitionSchema = z8.object({
1285
1287
  addDefaultPages: z8.boolean().optional(),
1286
1288
  addNavigation: z8.boolean().optional(),
1287
1289
  /** Root path for developer resource UI (e.g., '/access_requests'). Links internal model to a provision.pages entry. */
1288
- page: z8.string().optional()
1290
+ page: z8.string().optional(),
1291
+ env: EnvSchemaSchema.optional()
1289
1292
  });
1290
1293
  var RelationshipCardinalitySchema = z8.enum([
1291
1294
  "ONE_TO_ONE",
@@ -2097,6 +2100,56 @@ function getQueueConfigWithRetry(queueName) {
2097
2100
  return serializable;
2098
2101
  }
2099
2102
 
2103
+ // src/sequencer/config-loader.ts
2104
+ import * as fs2 from "fs";
2105
+ import * as path2 from "path";
2106
+ var cachedRuntimeSequencers = null;
2107
+ function getRuntimeConfigPath2() {
2108
+ if (process.env.LAMBDA_TASK_ROOT) {
2109
+ return path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json");
2110
+ }
2111
+ return path2.join(process.cwd(), ".skedyul", "config.json");
2112
+ }
2113
+ function loadRuntimeConfigFile2() {
2114
+ const configPath = getRuntimeConfigPath2();
2115
+ if (!fs2.existsSync(configPath)) {
2116
+ return null;
2117
+ }
2118
+ try {
2119
+ const raw = fs2.readFileSync(configPath, "utf-8");
2120
+ return JSON.parse(raw);
2121
+ } catch {
2122
+ return null;
2123
+ }
2124
+ }
2125
+ var registeredServerConfig2 = null;
2126
+ function registerSequencerConfig(config) {
2127
+ registeredServerConfig2 = config;
2128
+ cachedRuntimeSequencers = config.sequencers ?? null;
2129
+ }
2130
+ function clearRegisteredSequencerConfig() {
2131
+ registeredServerConfig2 = null;
2132
+ cachedRuntimeSequencers = null;
2133
+ }
2134
+ function getSequencerDefinitions() {
2135
+ if (cachedRuntimeSequencers) {
2136
+ return cachedRuntimeSequencers;
2137
+ }
2138
+ if (registeredServerConfig2?.sequencers) {
2139
+ cachedRuntimeSequencers = registeredServerConfig2.sequencers;
2140
+ return cachedRuntimeSequencers;
2141
+ }
2142
+ const fileConfig = loadRuntimeConfigFile2();
2143
+ if (fileConfig?.sequencers) {
2144
+ cachedRuntimeSequencers = fileConfig.sequencers;
2145
+ return cachedRuntimeSequencers;
2146
+ }
2147
+ return {};
2148
+ }
2149
+ function getSequencerConfig(sequencerName) {
2150
+ return getSequencerDefinitions()[sequencerName];
2151
+ }
2152
+
2100
2153
  // src/server/utils/schema.ts
2101
2154
  import * as z9 from "zod";
2102
2155
  function normalizeBilling(billing) {
@@ -2503,25 +2556,28 @@ function createInstanceClient(config) {
2503
2556
  pagination: pagination ?? { page: 1, total: 0, hasMore: false, limit: args?.limit ?? 50 }
2504
2557
  };
2505
2558
  },
2506
- async get(modelHandle, id) {
2559
+ async get(modelHandle, id, options) {
2507
2560
  const { data } = await callCoreWithConfig("instance.get", {
2508
2561
  modelHandle,
2509
- id
2562
+ id,
2563
+ ...options?.includeEnv ? { includeEnv: true } : {}
2510
2564
  });
2511
2565
  return data;
2512
2566
  },
2513
- async create(modelHandle, data) {
2567
+ async create(modelHandle, data, options) {
2514
2568
  const { data: inst } = await callCoreWithConfig("instance.create", {
2515
2569
  modelHandle,
2516
- data
2570
+ data,
2571
+ ...options?.env ? { env: options.env } : {}
2517
2572
  });
2518
2573
  return inst;
2519
2574
  },
2520
- async update(modelHandle, id, data) {
2575
+ async update(modelHandle, id, data, options) {
2521
2576
  const { data: inst } = await callCoreWithConfig("instance.update", {
2522
2577
  modelHandle,
2523
2578
  id,
2524
- data
2579
+ data,
2580
+ ...options?.env ? { env: options.env } : {}
2525
2581
  });
2526
2582
  return inst;
2527
2583
  },
@@ -2620,10 +2676,11 @@ var instance = {
2620
2676
  * const record = await instance.get('phone_number', 'ins_abc123')
2621
2677
  * ```
2622
2678
  */
2623
- async get(modelHandle, id) {
2679
+ async get(modelHandle, id, options) {
2624
2680
  const { data } = await callCore("instance.get", {
2625
2681
  modelHandle,
2626
- id
2682
+ id,
2683
+ ...options?.includeEnv ? { includeEnv: true } : {}
2627
2684
  });
2628
2685
  return data;
2629
2686
  },
@@ -2640,10 +2697,11 @@ var instance = {
2640
2697
  * })
2641
2698
  * ```
2642
2699
  */
2643
- async create(modelHandle, data) {
2700
+ async create(modelHandle, data, options) {
2644
2701
  const { data: instance2 } = await callCore("instance.create", {
2645
2702
  modelHandle,
2646
- data
2703
+ data,
2704
+ ...options?.env ? { env: options.env } : {}
2647
2705
  });
2648
2706
  return instance2;
2649
2707
  },
@@ -2660,11 +2718,12 @@ var instance = {
2660
2718
  * })
2661
2719
  * ```
2662
2720
  */
2663
- async update(modelHandle, id, data) {
2721
+ async update(modelHandle, id, data, options) {
2664
2722
  const { data: instance2 } = await callCore("instance.update", {
2665
2723
  modelHandle,
2666
2724
  id,
2667
- data
2725
+ data,
2726
+ ...options?.env ? { env: options.env } : {}
2668
2727
  });
2669
2728
  return instance2;
2670
2729
  },
@@ -4031,7 +4090,7 @@ function printStartupLog(config, tools, port) {
4031
4090
 
4032
4091
  // src/server/route-handlers/adapters.ts
4033
4092
  function fromLambdaEvent(event2) {
4034
- const path5 = event2.path || event2.rawPath || "/";
4093
+ const path6 = event2.path || event2.rawPath || "/";
4035
4094
  const method = event2.httpMethod || event2.requestContext?.http?.method || "POST";
4036
4095
  const forwardedProto = event2.headers?.["x-forwarded-proto"] ?? event2.headers?.["X-Forwarded-Proto"];
4037
4096
  const protocol = forwardedProto ?? "https";
@@ -4039,9 +4098,9 @@ function fromLambdaEvent(event2) {
4039
4098
  const queryString = event2.queryStringParameters ? "?" + new URLSearchParams(
4040
4099
  event2.queryStringParameters
4041
4100
  ).toString() : "";
4042
- const url = `${protocol}://${host}${path5}${queryString}`;
4101
+ const url = `${protocol}://${host}${path6}${queryString}`;
4043
4102
  return {
4044
- path: path5,
4103
+ path: path6,
4045
4104
  method,
4046
4105
  headers: event2.headers,
4047
4106
  query: event2.queryStringParameters ?? {},
@@ -4123,8 +4182,8 @@ function parseBodyByContentType(req) {
4123
4182
  }
4124
4183
 
4125
4184
  // src/server/route-handlers/handlers.ts
4126
- import * as fs2 from "fs";
4127
- import * as path2 from "path";
4185
+ import * as fs3 from "fs";
4186
+ import * as path3 from "path";
4128
4187
 
4129
4188
  // src/server/core-api-handler.ts
4130
4189
  async function handleCoreMethod(method, params) {
@@ -4592,7 +4651,7 @@ async function handleOAuthCallback(parsedBody, hooks) {
4592
4651
  }
4593
4652
 
4594
4653
  // src/server/handlers/webhook-handler.ts
4595
- function parseWebhookRequest(parsedBody, method, url, path5, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4654
+ function parseWebhookRequest(parsedBody, method, url, path6, headers, query, rawBody, appIdHeader, appVersionIdHeader) {
4596
4655
  const isEnvelope = typeof parsedBody === "object" && parsedBody !== null && "env" in parsedBody && "request" in parsedBody && "context" in parsedBody;
4597
4656
  if (isEnvelope) {
4598
4657
  const envelope = parsedBody;
@@ -4646,7 +4705,7 @@ function parseWebhookRequest(parsedBody, method, url, path5, headers, query, raw
4646
4705
  const webhookRequest = {
4647
4706
  method,
4648
4707
  url,
4649
- path: path5,
4708
+ path: path6,
4650
4709
  headers,
4651
4710
  query,
4652
4711
  body: parsedBody,
@@ -4675,9 +4734,17 @@ async function executeWebhookHandler(handle, webhookRegistry, data) {
4675
4734
  };
4676
4735
  let webhookResponse;
4677
4736
  try {
4737
+ const rateLimitContext = {
4738
+ app: data.webhookContext.app,
4739
+ appInstallationId: isRuntimeWebhookContext(data.webhookContext) ? data.webhookContext.appInstallationId : void 0,
4740
+ invocation: data.invocation,
4741
+ isProvisionContext: !isRuntimeWebhookContext(data.webhookContext)
4742
+ };
4678
4743
  webhookResponse = await runWithLogContext({ invocation: data.invocation }, async () => {
4679
4744
  return await runWithConfig(requestConfig, async () => {
4680
- return await webhookDef.handler(data.webhookRequest, data.webhookContext);
4745
+ return await runWithRateLimitExecutionContext(rateLimitContext, async () => {
4746
+ return await webhookDef.handler(data.webhookRequest, data.webhookContext);
4747
+ });
4681
4748
  });
4682
4749
  });
4683
4750
  } catch (err) {
@@ -4704,7 +4771,7 @@ function isMethodAllowed(webhookRegistry, handle, method) {
4704
4771
 
4705
4772
  // src/server/route-handlers/handlers.ts
4706
4773
  function getConfigFilePath() {
4707
- return process.env.LAMBDA_TASK_ROOT ? path2.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4774
+ return process.env.LAMBDA_TASK_ROOT ? path3.join(process.env.LAMBDA_TASK_ROOT, ".skedyul", "config.json") : ".skedyul/config.json";
4708
4775
  }
4709
4776
  function findToolInRegistry(registry, toolName) {
4710
4777
  for (const [key, t] of Object.entries(registry)) {
@@ -4743,8 +4810,8 @@ function handleHealthRoute(ctx) {
4743
4810
  function handleConfigRoute(ctx) {
4744
4811
  const configFilePath = getConfigFilePath();
4745
4812
  try {
4746
- if (fs2.existsSync(configFilePath)) {
4747
- const fileConfig = JSON.parse(fs2.readFileSync(configFilePath, "utf-8"));
4813
+ if (fs3.existsSync(configFilePath)) {
4814
+ const fileConfig = JSON.parse(fs3.readFileSync(configFilePath, "utf-8"));
4748
4815
  return { status: 200, body: fileConfig };
4749
4816
  }
4750
4817
  } catch (err) {
@@ -5432,6 +5499,7 @@ installContextLogger();
5432
5499
  function createSkedyulServer(config) {
5433
5500
  mergeRuntimeEnv();
5434
5501
  registerQueueConfig(config);
5502
+ registerSequencerConfig(config);
5435
5503
  const registry = config.tools;
5436
5504
  const webhookRegistry = config.webhooks;
5437
5505
  if (config.coreApi?.service) {
@@ -5700,13 +5768,13 @@ function defineNavigation(navigation) {
5700
5768
  }
5701
5769
 
5702
5770
  // src/config/schema-loader.ts
5703
- import * as fs3 from "fs";
5704
- import * as path3 from "path";
5771
+ import * as fs4 from "fs";
5772
+ import * as path4 from "path";
5705
5773
  import * as os from "os";
5706
5774
 
5707
5775
  // src/config/resolver.ts
5708
- import * as fs4 from "fs";
5709
- import * as path4 from "path";
5776
+ import * as fs5 from "fs";
5777
+ import * as path5 from "path";
5710
5778
 
5711
5779
  // src/config/utils.ts
5712
5780
  function getAllEnvKeys(config) {
@@ -5809,7 +5877,7 @@ function getApiToken() {
5809
5877
  const config = getConfig();
5810
5878
  return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
5811
5879
  }
5812
- async function callRateLimitApi(path5, body) {
5880
+ async function callRateLimitApi(path6, body) {
5813
5881
  const baseUrl = getApiBaseUrl();
5814
5882
  const token2 = getApiToken();
5815
5883
  if (!baseUrl || !token2) {
@@ -5817,7 +5885,7 @@ async function callRateLimitApi(path5, body) {
5817
5885
  "SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform queue coordination"
5818
5886
  );
5819
5887
  }
5820
- const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path5}`, {
5888
+ const response = await fetch(`${baseUrl}/api/internal/ratelimit/${path6}`, {
5821
5889
  method: "POST",
5822
5890
  headers: {
5823
5891
  Authorization: `Bearer ${token2}`,
@@ -5827,11 +5895,11 @@ async function callRateLimitApi(path5, body) {
5827
5895
  });
5828
5896
  const payload = await response.json().catch(() => null);
5829
5897
  if (!response.ok || payload?.success === false) {
5830
- const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path5} failed with status ${response.status}`;
5898
+ const message = payload?.errors?.[0]?.message ?? `Queue coordination API ${path6} failed with status ${response.status}`;
5831
5899
  throw new RateLimitBackendError(message, response.status);
5832
5900
  }
5833
5901
  if (!payload?.data) {
5834
- throw new RateLimitBackendError(`Queue coordination API ${path5} returned empty data`);
5902
+ throw new RateLimitBackendError(`Queue coordination API ${path6} returned empty data`);
5835
5903
  }
5836
5904
  return payload.data;
5837
5905
  }
@@ -6286,6 +6354,406 @@ async function queuedFetchResponse(queueInput, url, init) {
6286
6354
  return queuedFetch(queueInput, () => fetch(url, init));
6287
6355
  }
6288
6356
 
6357
+ // src/sequencer/errors.ts
6358
+ var SequencerContextError = class extends Error {
6359
+ constructor(message) {
6360
+ super(message);
6361
+ this.code = "SEQUENCER_CONTEXT_ERROR";
6362
+ this.name = "SequencerContextError";
6363
+ }
6364
+ };
6365
+ var SequencerNotFoundError = class extends Error {
6366
+ constructor(name) {
6367
+ super(`Sequencer "${name}" is not defined in skedyul.config`);
6368
+ this.code = "SEQUENCER_NOT_FOUND";
6369
+ this.name = "SequencerNotFoundError";
6370
+ }
6371
+ };
6372
+ var SequencerBackendError = class extends Error {
6373
+ constructor(message, statusCode) {
6374
+ super(message);
6375
+ this.code = "SEQUENCER_BACKEND_ERROR";
6376
+ this.name = "SequencerBackendError";
6377
+ this.statusCode = statusCode;
6378
+ }
6379
+ };
6380
+ var SequencerLockError = class extends Error {
6381
+ constructor(message) {
6382
+ super(message);
6383
+ this.code = "SEQUENCER_LOCK_ERROR";
6384
+ this.name = "SequencerLockError";
6385
+ }
6386
+ };
6387
+
6388
+ // src/sequencer/resolve-sequencer-key.ts
6389
+ function resolveEndpointHandle2(config, invocation) {
6390
+ if (config.endpoint) {
6391
+ return config.endpoint;
6392
+ }
6393
+ if (invocation?.toolHandle) {
6394
+ return invocation.toolHandle;
6395
+ }
6396
+ if (invocation?.serverHookHandle) {
6397
+ return invocation.serverHookHandle;
6398
+ }
6399
+ throw new SequencerContextError(
6400
+ `Cannot resolve endpoint handle for sequencer scope "${config.scope}". Set endpoint in sequencer config or ensure invocation.toolHandle/serverHookHandle is available.`
6401
+ );
6402
+ }
6403
+ function appendSubKey2(base, subKey) {
6404
+ return subKey ? `${base}:${subKey}` : base;
6405
+ }
6406
+ function resolveSequencerKey(sequencerName, config, ctx, subKey) {
6407
+ const { app, appInstallationId, invocation, isProvisionContext: isProvisionContext2 } = ctx;
6408
+ switch (config.scope) {
6409
+ case "provision": {
6410
+ const base = `seq:pv:${app.versionId}:${sequencerName}`;
6411
+ return appendSubKey2(base, subKey);
6412
+ }
6413
+ case "install": {
6414
+ if (!appInstallationId) {
6415
+ throw new SequencerContextError(
6416
+ `Sequencer "${sequencerName}" with scope "install" requires appInstallationId in context`
6417
+ );
6418
+ }
6419
+ const base = `seq:in:${appInstallationId}:${sequencerName}`;
6420
+ return appendSubKey2(base, subKey);
6421
+ }
6422
+ case "provision_endpoint": {
6423
+ if (!isProvisionContext2 && appInstallationId) {
6424
+ throw new SequencerContextError(
6425
+ `Sequencer "${sequencerName}" with scope "provision_endpoint" requires provision context (no install)`
6426
+ );
6427
+ }
6428
+ const endpointHandle = resolveEndpointHandle2(config, invocation);
6429
+ const base = `seq:pep:${app.versionId}:${endpointHandle}:${sequencerName}`;
6430
+ return appendSubKey2(base, subKey);
6431
+ }
6432
+ case "install_endpoint": {
6433
+ if (!appInstallationId) {
6434
+ throw new SequencerContextError(
6435
+ `Sequencer "${sequencerName}" with scope "install_endpoint" requires appInstallationId in context`
6436
+ );
6437
+ }
6438
+ const endpointHandle = resolveEndpointHandle2(config, invocation);
6439
+ const base = `seq:iep:${appInstallationId}:${endpointHandle}:${sequencerName}`;
6440
+ return appendSubKey2(base, subKey);
6441
+ }
6442
+ case "global": {
6443
+ const base = `seq:gl:${app.versionId}:${sequencerName}`;
6444
+ return appendSubKey2(base, subKey);
6445
+ }
6446
+ default: {
6447
+ const _exhaustive = config.scope;
6448
+ throw new SequencerContextError(`Unknown sequencer scope: ${String(_exhaustive)}`);
6449
+ }
6450
+ }
6451
+ }
6452
+
6453
+ // src/sequencer/backends/platform.ts
6454
+ function getApiBaseUrl2() {
6455
+ const config = getConfig();
6456
+ if (config.baseUrl) {
6457
+ return config.baseUrl.replace(/\/+$/, "");
6458
+ }
6459
+ return (process.env.SKEDYUL_API_URL ?? process.env.SKEDYUL_NODE_URL ?? "").replace(/\/+$/, "");
6460
+ }
6461
+ function getApiToken2() {
6462
+ const config = getConfig();
6463
+ return config.apiToken || process.env.SKEDYUL_API_TOKEN || "";
6464
+ }
6465
+ async function callSequencerApi(path6, body) {
6466
+ const baseUrl = getApiBaseUrl2();
6467
+ const token2 = getApiToken2();
6468
+ if (!baseUrl || !token2) {
6469
+ throw new SequencerBackendError(
6470
+ "SKEDYUL_API_URL and SKEDYUL_API_TOKEN are required for platform sequencer coordination"
6471
+ );
6472
+ }
6473
+ const response = await fetch(`${baseUrl}/api/internal/sequencer/${path6}`, {
6474
+ method: "POST",
6475
+ headers: {
6476
+ Authorization: `Bearer ${token2}`,
6477
+ "Content-Type": "application/json"
6478
+ },
6479
+ body: JSON.stringify(body)
6480
+ });
6481
+ const payload = await response.json().catch(() => null);
6482
+ if (!response.ok || payload?.success === false) {
6483
+ const message = payload?.errors?.[0]?.message ?? `Sequencer coordination API ${path6} failed with status ${response.status}`;
6484
+ throw new SequencerBackendError(message, response.status);
6485
+ }
6486
+ if (!payload?.data) {
6487
+ throw new SequencerBackendError(`Sequencer coordination API ${path6} returned empty data`);
6488
+ }
6489
+ return payload.data;
6490
+ }
6491
+ var PlatformSequencerBackend = class {
6492
+ async allow(sequencerKey, input) {
6493
+ return callSequencerApi("allow", {
6494
+ sequencerKey,
6495
+ timestamp: input.timestamp,
6496
+ leaseId: input.leaseId,
6497
+ watermarkTtlMs: input.watermarkTtlMs
6498
+ });
6499
+ }
6500
+ async acquire(sequencerKey, input) {
6501
+ return callSequencerApi("acquire", {
6502
+ sequencerKey,
6503
+ leaseId: input.leaseId,
6504
+ timestamp: input.timestamp,
6505
+ lockTtlMs: input.lockTtlMs,
6506
+ watermarkTtlMs: input.watermarkTtlMs
6507
+ });
6508
+ }
6509
+ async release(sequencerKey, leaseId) {
6510
+ await callSequencerApi("release", {
6511
+ sequencerKey,
6512
+ leaseId
6513
+ });
6514
+ }
6515
+ };
6516
+ var platformSequencerBackend = new PlatformSequencerBackend();
6517
+
6518
+ // src/sequencer/backends/memory.ts
6519
+ function generateLeaseId2() {
6520
+ return `mem_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
6521
+ }
6522
+ var MemorySequencerBackend = class {
6523
+ constructor() {
6524
+ this.state = /* @__PURE__ */ new Map();
6525
+ }
6526
+ getState(sequencerKey) {
6527
+ let state = this.state.get(sequencerKey);
6528
+ if (!state) {
6529
+ state = { watermark: 0, lockLeaseId: null, lockExpiresAt: 0 };
6530
+ this.state.set(sequencerKey, state);
6531
+ }
6532
+ if (state.lockLeaseId && state.lockExpiresAt <= Date.now()) {
6533
+ state.lockLeaseId = null;
6534
+ state.lockExpiresAt = 0;
6535
+ }
6536
+ return state;
6537
+ }
6538
+ async allow(sequencerKey, input) {
6539
+ void input.watermarkTtlMs;
6540
+ const state = this.getState(sequencerKey);
6541
+ if (input.timestamp < state.watermark) {
6542
+ return { allowed: false, reason: "stale", watermark: state.watermark };
6543
+ }
6544
+ if (state.lockLeaseId && input.leaseId && state.lockLeaseId !== input.leaseId) {
6545
+ return { allowed: false, reason: "locked", watermark: state.watermark };
6546
+ }
6547
+ state.watermark = Math.max(state.watermark, input.timestamp);
6548
+ return { allowed: true, reason: "ok", watermark: state.watermark };
6549
+ }
6550
+ async acquire(sequencerKey, input) {
6551
+ void input.watermarkTtlMs;
6552
+ const state = this.getState(sequencerKey);
6553
+ const leaseId = input.leaseId || generateLeaseId2();
6554
+ if (state.lockLeaseId && state.lockLeaseId !== leaseId) {
6555
+ return { acquired: false, reason: "locked" };
6556
+ }
6557
+ state.lockLeaseId = leaseId;
6558
+ state.lockExpiresAt = Date.now() + (input.lockTtlMs ?? 6e4);
6559
+ if (input.timestamp !== void 0) {
6560
+ state.watermark = Math.max(state.watermark, input.timestamp);
6561
+ }
6562
+ return { acquired: true, reason: "ok" };
6563
+ }
6564
+ async release(sequencerKey, leaseId) {
6565
+ const state = this.state.get(sequencerKey);
6566
+ if (!state || state.lockLeaseId !== leaseId) {
6567
+ return;
6568
+ }
6569
+ state.lockLeaseId = null;
6570
+ state.lockExpiresAt = 0;
6571
+ }
6572
+ resetForTests() {
6573
+ this.state.clear();
6574
+ }
6575
+ };
6576
+ var memorySequencerBackend = new MemorySequencerBackend();
6577
+
6578
+ // src/sequencer/backends/index.ts
6579
+ var cachedBackend2 = null;
6580
+ function shouldForceMemoryBackend2() {
6581
+ const flag = process.env.SKEDYUL_SEQUENCER_MEMORY;
6582
+ return flag === "1" || flag === "true";
6583
+ }
6584
+ function getSequencerBackend() {
6585
+ if (cachedBackend2) {
6586
+ return cachedBackend2;
6587
+ }
6588
+ if (shouldForceMemoryBackend2()) {
6589
+ cachedBackend2 = memorySequencerBackend;
6590
+ return cachedBackend2;
6591
+ }
6592
+ cachedBackend2 = createResilientBackend2();
6593
+ return cachedBackend2;
6594
+ }
6595
+ function createResilientBackend2() {
6596
+ return {
6597
+ async allow(sequencerKey, input) {
6598
+ try {
6599
+ return await platformSequencerBackend.allow(sequencerKey, input);
6600
+ } catch (error) {
6601
+ if (shouldFallbackToMemory2(error)) {
6602
+ return memorySequencerBackend.allow(sequencerKey, input);
6603
+ }
6604
+ throw error;
6605
+ }
6606
+ },
6607
+ async acquire(sequencerKey, input) {
6608
+ try {
6609
+ return await platformSequencerBackend.acquire(sequencerKey, input);
6610
+ } catch (error) {
6611
+ if (shouldFallbackToMemory2(error)) {
6612
+ return memorySequencerBackend.acquire(sequencerKey, input);
6613
+ }
6614
+ throw error;
6615
+ }
6616
+ },
6617
+ async release(sequencerKey, leaseId) {
6618
+ if (leaseId.startsWith("mem_")) {
6619
+ return memorySequencerBackend.release(sequencerKey, leaseId);
6620
+ }
6621
+ try {
6622
+ await platformSequencerBackend.release(sequencerKey, leaseId);
6623
+ } catch (error) {
6624
+ if (shouldFallbackToMemory2(error)) {
6625
+ return memorySequencerBackend.release(sequencerKey, leaseId);
6626
+ }
6627
+ throw error;
6628
+ }
6629
+ }
6630
+ };
6631
+ }
6632
+ function shouldFallbackToMemory2(error) {
6633
+ if (shouldForceMemoryBackend2()) {
6634
+ return true;
6635
+ }
6636
+ if (error instanceof SequencerBackendError) {
6637
+ if (error.statusCode === 404 || error.statusCode === 503) {
6638
+ return true;
6639
+ }
6640
+ }
6641
+ if (error instanceof TypeError) {
6642
+ return true;
6643
+ }
6644
+ const nodeEnv = process.env.NODE_ENV;
6645
+ return nodeEnv === "development" || nodeEnv === "test";
6646
+ }
6647
+ function resetSequencerBackendForTests() {
6648
+ cachedBackend2 = null;
6649
+ }
6650
+
6651
+ // src/sequencer/index.ts
6652
+ function normalizeSequencerInput(input) {
6653
+ if (typeof input === "string") {
6654
+ return { name: input };
6655
+ }
6656
+ return { name: input.sequencer, subKey: input.key };
6657
+ }
6658
+ function mergeContext(ctxOverride) {
6659
+ const ctx = getRateLimitExecutionContext();
6660
+ if (!ctx?.app?.versionId) {
6661
+ throw new SequencerContextError(
6662
+ "Cannot resolve sequencer without app context. Ensure sequencer runs inside a tool/hook/webhook handler."
6663
+ );
6664
+ }
6665
+ return { ...ctx, ...ctxOverride };
6666
+ }
6667
+ function resolveSequencer(sequencerInput, ctxOverride) {
6668
+ const ctx = mergeContext(ctxOverride);
6669
+ const { name, subKey } = normalizeSequencerInput(sequencerInput);
6670
+ const config = getSequencerConfig(name);
6671
+ if (!config) {
6672
+ throw new SequencerNotFoundError(name);
6673
+ }
6674
+ const sequencerKey = resolveSequencerKey(name, config, ctx, subKey);
6675
+ return { name, sequencerKey, config };
6676
+ }
6677
+ function createSequencerHandle(sequencerInput, ctxOverride) {
6678
+ return {
6679
+ allow(input) {
6680
+ return allow(sequencerInput, input, ctxOverride);
6681
+ },
6682
+ acquire(input) {
6683
+ return acquire(sequencerInput, input, ctxOverride);
6684
+ },
6685
+ release(input) {
6686
+ return release(sequencerInput, input, ctxOverride);
6687
+ }
6688
+ };
6689
+ }
6690
+ function disabledAllowResult() {
6691
+ return { allowed: true, reason: "disabled" };
6692
+ }
6693
+ async function allow(sequencerInput, input, ctxOverride) {
6694
+ const { name, subKey } = normalizeSequencerInput(sequencerInput);
6695
+ const config = getSequencerConfig(name);
6696
+ if (!config || config.enabled !== true) {
6697
+ return disabledAllowResult();
6698
+ }
6699
+ const resolvedSubKey = input.key ?? subKey;
6700
+ const resolved = resolveSequencer(
6701
+ { sequencer: name, key: resolvedSubKey },
6702
+ ctxOverride
6703
+ );
6704
+ const backend = getSequencerBackend();
6705
+ return backend.allow(resolved.sequencerKey, {
6706
+ timestamp: input.timestamp,
6707
+ leaseId: input.leaseId,
6708
+ watermarkTtlMs: resolved.config.watermarkTtlMs
6709
+ });
6710
+ }
6711
+ async function acquire(sequencerInput, input, ctxOverride) {
6712
+ const { name, subKey } = normalizeSequencerInput(sequencerInput);
6713
+ const config = getSequencerConfig(name);
6714
+ if (!config || config.enabled !== true) {
6715
+ return;
6716
+ }
6717
+ const resolvedSubKey = input.key ?? subKey;
6718
+ const resolved = resolveSequencer(
6719
+ { sequencer: name, key: resolvedSubKey },
6720
+ ctxOverride
6721
+ );
6722
+ const backend = getSequencerBackend();
6723
+ const result = await backend.acquire(resolved.sequencerKey, {
6724
+ leaseId: input.leaseId,
6725
+ timestamp: input.timestamp,
6726
+ lockTtlMs: input.lockTtlMs ?? resolved.config.lockTtlMs,
6727
+ watermarkTtlMs: resolved.config.watermarkTtlMs
6728
+ });
6729
+ if (!result.acquired) {
6730
+ throw new SequencerLockError(
6731
+ `Sequencer "${name}" lock held by another lease (${result.reason})`
6732
+ );
6733
+ }
6734
+ }
6735
+ async function release(sequencerInput, input, ctxOverride) {
6736
+ const { name, subKey } = normalizeSequencerInput(sequencerInput);
6737
+ const config = getSequencerConfig(name);
6738
+ if (!config || config.enabled !== true) {
6739
+ return;
6740
+ }
6741
+ const resolvedSubKey = input.key ?? subKey;
6742
+ const resolved = resolveSequencer(
6743
+ { sequencer: name, key: resolvedSubKey },
6744
+ ctxOverride
6745
+ );
6746
+ const backend = getSequencerBackend();
6747
+ await backend.release(resolved.sequencerKey, input.leaseId);
6748
+ }
6749
+ var sequencer = {
6750
+ allow,
6751
+ acquire,
6752
+ release,
6753
+ resolve: resolveSequencer,
6754
+ createHandle: createSequencerHandle
6755
+ };
6756
+
6289
6757
  // src/triggers/types.ts
6290
6758
  import { z as z12 } from "zod/v4";
6291
6759
  var CRMDataSchema = z12.object({
@@ -6390,16 +6858,16 @@ function evaluateTemplate(template, context) {
6390
6858
  const templateRegex = /\{\{\s*([^}]+)\s*\}\}/g;
6391
6859
  const singleMatch = template.match(/^\{\{\s*([^}]+)\s*\}\}$/);
6392
6860
  if (singleMatch) {
6393
- const path5 = singleMatch[1].trim();
6394
- return resolvePath(context, path5);
6861
+ const path6 = singleMatch[1].trim();
6862
+ return resolvePath(context, path6);
6395
6863
  }
6396
- return template.replace(templateRegex, (_, path5) => {
6397
- const value = resolvePath(context, path5.trim());
6864
+ return template.replace(templateRegex, (_, path6) => {
6865
+ const value = resolvePath(context, path6.trim());
6398
6866
  return value === void 0 || value === null ? "" : String(value);
6399
6867
  });
6400
6868
  }
6401
- function resolvePath(obj, path5) {
6402
- const parts = path5.split(".");
6869
+ function resolvePath(obj, path6) {
6870
+ const parts = path6.split(".");
6403
6871
  let current = obj;
6404
6872
  for (const part of parts) {
6405
6873
  if (current === null || current === void 0) {
@@ -6420,14 +6888,14 @@ function evaluateCondition(condition, context) {
6420
6888
  const expression = match[1].trim();
6421
6889
  const eqMatch = expression.match(/^(.+?)\s*==\s*['"](.+)['"]$/);
6422
6890
  if (eqMatch) {
6423
- const [, path5, expectedValue] = eqMatch;
6424
- const actualValue = resolvePath(context, path5.trim());
6891
+ const [, path6, expectedValue] = eqMatch;
6892
+ const actualValue = resolvePath(context, path6.trim());
6425
6893
  return actualValue === expectedValue;
6426
6894
  }
6427
6895
  const neqMatch = expression.match(/^(.+?)\s*!=\s*['"](.+)['"]$/);
6428
6896
  if (neqMatch) {
6429
- const [, path5, expectedValue] = neqMatch;
6430
- const actualValue = resolvePath(context, path5.trim());
6897
+ const [, path6, expectedValue] = neqMatch;
6898
+ const actualValue = resolvePath(context, path6.trim());
6431
6899
  return actualValue !== expectedValue;
6432
6900
  }
6433
6901
  const value = resolvePath(context, expression);
@@ -7879,6 +8347,10 @@ export {
7879
8347
  SelectComponentDefinitionSchema,
7880
8348
  SemanticMemoryConfigSchema2 as SemanticMemoryConfigSchema,
7881
8349
  SenderContextSchema,
8350
+ SequencerBackendError,
8351
+ SequencerContextError,
8352
+ SequencerLockError,
8353
+ SequencerNotFoundError,
7882
8354
  SkedyulConfigSchema,
7883
8355
  SkillExampleSchema,
7884
8356
  SkillMetadataSchema,
@@ -7934,6 +8406,7 @@ export {
7934
8406
  buildAgentContext,
7935
8407
  calculateWaitTime,
7936
8408
  call,
8409
+ clearRegisteredSequencerConfig,
7937
8410
  communicationChannel,
7938
8411
  compileAgent,
7939
8412
  compileWorkflow,
@@ -7953,6 +8426,7 @@ export {
7953
8426
  createPermissionError,
7954
8427
  createQueueHandle,
7955
8428
  createRateLimitError,
8429
+ createSequencerHandle,
7956
8430
  createServerHookContext,
7957
8431
  createSuccessResponse,
7958
8432
  createTimeoutError,
@@ -7991,6 +8465,8 @@ export {
7991
8465
  getRateLimitExecutionContext,
7992
8466
  getRequiredInstallEnvKeys,
7993
8467
  getRetryDelay,
8468
+ getSequencerConfig,
8469
+ getSequencerDefinitions,
7994
8470
  instance,
7995
8471
  isChannelDependency,
7996
8472
  isCronContext,
@@ -8006,20 +8482,29 @@ export {
8006
8482
  isTimeInWindowSlot2 as isTimeInWindowSlot,
8007
8483
  isWorkflowDependency,
8008
8484
  matchesTrigger,
8485
+ memorySequencerBackend,
8009
8486
  parseCRMSchema,
8010
8487
  parseEstimationFromBilling,
8488
+ platformSequencerBackend,
8011
8489
  queuedFetch,
8012
8490
  queuedFetchResponse,
8013
8491
  registerQueueConfig,
8492
+ registerSequencerConfig,
8014
8493
  report,
8015
8494
  requeue,
8495
+ resetSequencerBackendForTests,
8016
8496
  resolveInputMappings,
8017
8497
  resolveQueue,
8498
+ resolveSequencer,
8018
8499
  resource,
8019
8500
  runWithConfig,
8020
8501
  runWithRateLimitExecutionContext,
8021
8502
  safeParseCRMSchema,
8022
8503
  safeParseConfig,
8504
+ sequencer,
8505
+ acquire as sequencerAcquire,
8506
+ allow as sequencerAllow,
8507
+ release as sequencerRelease,
8023
8508
  server,
8024
8509
  toGsm7,
8025
8510
  token,