@automagik/omni 2.260911.3 → 2.260911.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.
@@ -38333,6 +38333,8 @@ class DebounceManager {
38333
38333
  this.windows.set(key, window2);
38334
38334
  }
38335
38335
  window2.messages.push(message2);
38336
+ window2.from = from;
38337
+ window2.instanceId = instanceId;
38336
38338
  window2.lastActivityAt = Date.now();
38337
38339
  this.resetTimer(key, window2);
38338
38340
  }
@@ -38667,6 +38669,23 @@ class AutomationEngine {
38667
38669
  throw err2;
38668
38670
  }
38669
38671
  }
38672
+ resolveDebounceKey(automation, event, instanceId) {
38673
+ const payload = event.payload;
38674
+ const template = automation.debounce?.key;
38675
+ if (!template) {
38676
+ const from = payload.from;
38677
+ return from?.id ? buildConversationKey(instanceId, from.id) : null;
38678
+ }
38679
+ const rendered = substituteTemplate(template, createTemplateContext(payload)).trim();
38680
+ if (!rendered) {
38681
+ logger4.debug("Debounce key template rendered empty \u2014 processing immediately", {
38682
+ automationId: automation.id,
38683
+ eventId: event.id
38684
+ });
38685
+ return null;
38686
+ }
38687
+ return `${instanceId}:${rendered}`;
38688
+ }
38670
38689
  async handleDebounced(automation, event) {
38671
38690
  const manager = this.debounceManagers.get(automation.id);
38672
38691
  if (!manager) {
@@ -38676,11 +38695,11 @@ class AutomationEngine {
38676
38695
  const payload = event.payload;
38677
38696
  const instanceId = event.metadata.instanceId ?? "global";
38678
38697
  const from = payload.from;
38679
- if (!from?.id) {
38698
+ const key = this.resolveDebounceKey(automation, event, instanceId);
38699
+ if (!key) {
38680
38700
  await this.handleImmediate(automation, event);
38681
38701
  return;
38682
38702
  }
38683
- const key = buildConversationKey(instanceId, from.id);
38684
38703
  if (event.type.startsWith("presence.")) {
38685
38704
  manager.handlePresenceEvent(key, event.type);
38686
38705
  return;
@@ -251672,7 +251691,7 @@ var init_sentry_scrub = __esm(() => {
251672
251691
  var require_package7 = __commonJS((exports, module) => {
251673
251692
  module.exports = {
251674
251693
  name: "@omni/api",
251675
- version: "2.260911.3",
251694
+ version: "2.260911.5",
251676
251695
  private: true,
251677
251696
  type: "module",
251678
251697
  exports: {
@@ -375856,6 +375875,9 @@ var init_ingress_idempotency = __esm(() => {
375856
375875
 
375857
375876
  // ../api/src/services/webhooks.ts
375858
375877
  import { createHmac as createHmac4, timingSafeEqual } from "crypto";
375878
+ function scopeTriggerKey(key, instanceId, tenantId) {
375879
+ return `manual:${instanceId ?? tenantId ?? "global"}:${key}`;
375880
+ }
375859
375881
  function timingSafeEqualStrings(a, b3) {
375860
375882
  const bufA = Buffer.from(a);
375861
375883
  const bufB = Buffer.from(b3);
@@ -376026,7 +376048,7 @@ class WebhookService {
376026
376048
  payload,
376027
376049
  headers
376028
376050
  });
376029
- const claimed = await this.db.insert(omniEvents).values({
376051
+ const claim = await this.claimJournalRow({
376030
376052
  id: eventId,
376031
376053
  channel: "internal",
376032
376054
  eventType,
@@ -376036,18 +376058,17 @@ class WebhookService {
376036
376058
  idempotencyKey,
376037
376059
  receivedAt: new Date,
376038
376060
  metadata: { correlationId: eventId, source: "webhook", fullEventType: eventType, webhookSource: sourceName }
376039
- }).onConflictDoNothing({ target: omniEvents.idempotencyKey }).returning({ id: omniEvents.id });
376040
- if (claimed.length === 0) {
376061
+ });
376062
+ if (claim.duplicate) {
376041
376063
  await this.db.update(webhookSources).set({
376042
376064
  totalDuplicates: sql`${webhookSources.totalDuplicates} + 1`,
376043
376065
  updatedAt: new Date
376044
376066
  }).where(eq(webhookSources.id, source.id));
376045
- const [original] = await this.db.select({ id: omniEvents.id }).from(omniEvents).where(eq(omniEvents.idempotencyKey, idempotencyKey)).limit(1);
376046
376067
  log114.info("Webhook redelivery acked without a second event", { sourceName, idempotencyKey });
376047
376068
  return {
376048
376069
  received: true,
376049
376070
  duplicate: true,
376050
- eventId: original?.id ?? eventId,
376071
+ eventId: claim.eventId,
376051
376072
  source: sourceName,
376052
376073
  eventType
376053
376074
  };
@@ -376082,6 +376103,14 @@ class WebhookService {
376082
376103
  eventType
376083
376104
  };
376084
376105
  }
376106
+ async claimJournalRow(row) {
376107
+ const claimed = await this.db.insert(omniEvents).values(row).onConflictDoNothing({ target: omniEvents.idempotencyKey }).returning({ id: omniEvents.id });
376108
+ if (claimed.length > 0) {
376109
+ return { eventId: row.id, duplicate: false };
376110
+ }
376111
+ const [original] = await this.db.select({ id: omniEvents.id }).from(omniEvents).where(eq(omniEvents.idempotencyKey, row.idempotencyKey)).limit(1);
376112
+ return { eventId: original?.id ?? row.id, duplicate: true };
376113
+ }
376085
376114
  verifySignature(source, rawBody, headers) {
376086
376115
  const config4 = source.signatureConfig;
376087
376116
  if (!config4)
@@ -376145,16 +376174,66 @@ class WebhookService {
376145
376174
  async trigger(eventType, payload, metadata) {
376146
376175
  const provisionalId = metadata?.correlationId ?? generateId();
376147
376176
  await this.enforceRegisteredSchema(eventType, payload, provisionalId, "manual trigger");
376148
- if (this.eventBus) {
376149
- const result = await this.eventBus.publishGeneric(eventType, payload, {
376150
- correlationId: metadata?.correlationId,
376151
- causationId: metadata?.causationId,
376152
- instanceId: metadata?.instanceId,
376177
+ const bus2 = this.eventBus;
376178
+ if (!bus2) {
376179
+ return { eventId: provisionalId, published: false };
376180
+ }
376181
+ const key = metadata?.idempotencyKey;
376182
+ if (key) {
376183
+ return this.triggerClaimed(bus2, eventType, payload, metadata ?? {}, key);
376184
+ }
376185
+ const result = await bus2.publishGeneric(eventType, payload, {
376186
+ correlationId: metadata?.correlationId,
376187
+ causationId: metadata?.causationId,
376188
+ instanceId: metadata?.instanceId,
376189
+ source: "manual-trigger"
376190
+ });
376191
+ return { eventId: result.id, published: true };
376192
+ }
376193
+ async triggerClaimed(bus2, eventType, payload, metadata, key) {
376194
+ const idempotencyKey = scopeTriggerKey(key, metadata.instanceId, this.tenantId);
376195
+ const eventId = generateId();
376196
+ const claim = await this.claimJournalRow({
376197
+ id: eventId,
376198
+ channel: "internal",
376199
+ eventType,
376200
+ direction: "internal",
376201
+ status: "completed",
376202
+ rawPayload: payload,
376203
+ idempotencyKey,
376204
+ causationId: metadata.causationId ?? null,
376205
+ receivedAt: new Date,
376206
+ metadata: {
376207
+ correlationId: metadata.correlationId ?? eventId,
376208
+ source: "manual-trigger",
376209
+ fullEventType: eventType,
376210
+ idempotencyKey
376211
+ }
376212
+ });
376213
+ if (claim.duplicate) {
376214
+ log114.info("Manual trigger deduped: idempotency key already journaled", { eventType, idempotencyKey });
376215
+ return { eventId: claim.eventId, published: false, duplicate: true };
376216
+ }
376217
+ try {
376218
+ await bus2.publishGeneric(eventType, payload, {
376219
+ correlationId: metadata.correlationId,
376220
+ causationId: metadata.causationId,
376221
+ instanceId: metadata.instanceId,
376222
+ publishEventId: eventId,
376153
376223
  source: "manual-trigger"
376154
376224
  });
376155
- return { eventId: result.id, published: true };
376225
+ } catch (error3) {
376226
+ await this.db.delete(omniEvents).where(eq(omniEvents.id, eventId)).catch((releaseError) => {
376227
+ log114.error("Failed to release trigger idempotency claim after publish failure", {
376228
+ eventType,
376229
+ eventId,
376230
+ idempotencyKey,
376231
+ error: String(releaseError)
376232
+ });
376233
+ });
376234
+ throw error3;
376156
376235
  }
376157
- return { eventId: provisionalId, published: false };
376236
+ return { eventId, published: true, duplicate: false };
376158
376237
  }
376159
376238
  async heartbeat(sourceName) {
376160
376239
  const source = await this.getByName(sourceName);
@@ -379381,7 +379460,8 @@ var init_automations3 = __esm(() => {
379381
379460
  maxMs: exports_external.number().int().optional(),
379382
379461
  baseDelayMs: exports_external.number().int().optional(),
379383
379462
  maxWaitMs: exports_external.number().int().optional(),
379384
- extendOnEvents: exports_external.array(exports_external.string()).optional()
379463
+ extendOnEvents: exports_external.array(exports_external.string()).optional(),
379464
+ key: exports_external.string().optional().describe('Template over the event payload naming the debounce window (#1110), e.g. "{{payload.pull_request.id}}"; ' + 'absent = grouped by conversation (instance + sender). Not allowed with mode "presence"')
379385
379465
  });
379386
379466
  AutomationSchema = exports_external.object({
379387
379467
  id: exports_external.string().uuid().openapi({ description: "Automation UUID" }),
@@ -383545,7 +383625,10 @@ var init_webhooks2 = __esm(() => {
383545
383625
  payload: exports_external.record(exports_external.string(), exports_external.unknown()).openapi({ description: "Event payload" }),
383546
383626
  correlationId: exports_external.string().optional().openapi({ description: "Correlation ID" }),
383547
383627
  causationId: exports_external.string().uuid().optional().openapi({ description: "Parent event ID; stamps causationId so the emission is parented in the causality tree" }),
383548
- instanceId: exports_external.string().uuid().optional().openapi({ description: "Instance ID for context" })
383628
+ instanceId: exports_external.string().uuid().optional().openapi({ description: "Instance ID for context" }),
383629
+ idempotencyKey: exports_external.string().min(1).max(255).optional().openapi({
383630
+ description: "Producer-supplied ingress key (#1109). Unique per instance (else per tenant), not global. A key already journaled publishes nothing and returns the ORIGINAL event id with duplicate: true. Omit for undeduped publishing."
383631
+ })
383549
383632
  });
383550
383633
  WebhookReceiveResponseSchema = exports_external.object({
383551
383634
  eventId: exports_external.string().uuid().openapi({ description: "Created event ID (the ORIGINAL event on a duplicate)" }),
@@ -385132,7 +385215,7 @@ async function resolveTestEvent(services, body) {
385132
385215
  timestamp: row.receivedAt.getTime()
385133
385216
  };
385134
385217
  }
385135
- var automationsRoutes, conditionSchema, webhookActionSchema, sendMessageActionSchema, emitEventActionSchema, logActionSchema, callAgentActionSchema, actionSchema, debounceSchema, createAutomationSchema, updateAutomationSchema, listQuerySchema5, testAutomationSchema, logsQuerySchema;
385218
+ var automationsRoutes, conditionSchema, webhookActionSchema, sendMessageActionSchema, emitEventActionSchema, logActionSchema, callAgentActionSchema, actionSchema, debounceKeySchema, debounceSchema, createAutomationSchema, updateAutomationSchema, listQuerySchema5, testAutomationSchema, logsQuerySchema;
385136
385219
  var init_automations4 = __esm(() => {
385137
385220
  init_dist6();
385138
385221
  init_dist2();
@@ -385198,24 +385281,31 @@ var init_automations4 = __esm(() => {
385198
385281
  logActionSchema,
385199
385282
  callAgentActionSchema
385200
385283
  ]);
385284
+ debounceKeySchema = exports_external.string().min(1).optional().describe('Template over the event payload naming the debounce window (#1110), e.g. "{{payload.pull_request.id}}"; ' + 'omit to group by conversation (instance + sender) as before. Not allowed with mode "presence"');
385201
385285
  debounceSchema = exports_external.discriminatedUnion("mode", [
385202
- exports_external.object({ mode: exports_external.literal("none") }),
385286
+ exports_external.object({ mode: exports_external.literal("none"), key: debounceKeySchema }),
385203
385287
  exports_external.object({
385204
385288
  mode: exports_external.literal("fixed"),
385205
- delayMs: exports_external.number().int().min(100).max(300000).describe("Fixed delay in milliseconds")
385289
+ delayMs: exports_external.number().int().min(100).max(300000).describe("Fixed delay in milliseconds"),
385290
+ key: debounceKeySchema
385206
385291
  }),
385207
385292
  exports_external.object({
385208
385293
  mode: exports_external.literal("range"),
385209
385294
  minMs: exports_external.number().int().min(100).describe("Minimum delay"),
385210
- maxMs: exports_external.number().int().max(300000).describe("Maximum delay")
385295
+ maxMs: exports_external.number().int().max(300000).describe("Maximum delay"),
385296
+ key: debounceKeySchema
385211
385297
  }),
385212
385298
  exports_external.object({
385213
385299
  mode: exports_external.literal("presence"),
385214
385300
  baseDelayMs: exports_external.number().int().min(100).describe("Base delay"),
385215
385301
  maxWaitMs: exports_external.number().int().max(300000).optional().describe("Maximum total wait"),
385216
- extendOnEvents: exports_external.array(exports_external.string()).describe("Events that extend the timer")
385302
+ extendOnEvents: exports_external.array(exports_external.string()).describe("Events that extend the timer"),
385303
+ key: debounceKeySchema
385217
385304
  })
385218
- ]);
385305
+ ]).refine((debounce3) => !(debounce3.mode === "presence" && debounce3.key), {
385306
+ message: 'debounce.key cannot be combined with mode "presence": presence extension only applies to conversations',
385307
+ path: ["key"]
385308
+ });
385219
385309
  createAutomationSchema = exports_external.object({
385220
385310
  name: exports_external.string().min(1).max(255).describe("Automation name"),
385221
385311
  description: exports_external.string().optional().describe("Description"),
@@ -396356,10 +396446,11 @@ var init_webhooks3 = __esm(() => {
396356
396446
  payload: exports_external.record(exports_external.string(), exports_external.unknown()).describe("Event payload"),
396357
396447
  correlationId: exports_external.string().optional().describe("Optional correlation ID"),
396358
396448
  causationId: exports_external.string().uuid().optional().describe("Optional parent event ID (causality tree, #1072)"),
396359
- instanceId: exports_external.string().uuid().optional().describe("Optional instance ID for context")
396449
+ instanceId: exports_external.string().uuid().optional().describe("Optional instance ID for context"),
396450
+ idempotencyKey: exports_external.string().min(1).max(255).optional().describe("Optional producer-supplied ingress key; a key already journaled returns the original event (#1109)")
396360
396451
  });
396361
396452
  webhooksRoutes.post("/events/trigger", zValidator("json", triggerEventSchema), async (c) => {
396362
- const { eventType, payload, correlationId, causationId, instanceId } = c.req.valid("json");
396453
+ const { eventType, payload, correlationId, causationId, instanceId, idempotencyKey } = c.req.valid("json");
396363
396454
  const services = c.get("services");
396364
396455
  const apiKey = c.get("apiKey");
396365
396456
  if (instanceId && apiKey && !ApiKeyService.instanceAllowed(apiKey.instanceIds, instanceId)) {
@@ -396368,7 +396459,8 @@ var init_webhooks3 = __esm(() => {
396368
396459
  const result = await services.webhooks.trigger(eventType, payload, {
396369
396460
  correlationId,
396370
396461
  causationId,
396371
- instanceId
396462
+ instanceId,
396463
+ idempotencyKey
396372
396464
  });
396373
396465
  return c.json(result, 201);
396374
396466
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260911.3",
3
+ "version": "2.260911.5",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/automagik-dev/omni.git",