@automagik/omni 2.260906.2 → 2.260906.3

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.
@@ -0,0 +1,40 @@
1
+ -- Connector lifecycle contract (#961) — liveness, heartbeat, declared
2
+ -- window/mutation semantics on webhook_sources.
3
+ --
4
+ -- A source that declares expected_interval_seconds promises "≥1 event or
5
+ -- heartbeat per N seconds". The liveness sweeper compares the most recent
6
+ -- signal — GREATEST(last_received_at, last_heartbeat_at, liveness_armed_at) —
7
+ -- against that window and owns every liveness_status transition, so
8
+ -- system.connector.stalled / system.connector.recovered are emitted exactly
9
+ -- once per transition.
10
+ --
11
+ -- expected_interval_seconds integer declared cadence; NULL = unsupervised
12
+ -- last_heartbeat_at timestamptz "I ran, zero events found" — the
13
+ -- heartbeat verb's compacted trace
14
+ -- (no journal event per heartbeat)
15
+ -- heartbeat_count integer total heartbeats received
16
+ -- liveness_status varchar(20) 'healthy' | 'stalled'; NULL = unsupervised
17
+ -- liveness_armed_at timestamptz when the cadence was (re)declared —
18
+ -- a fresh window before a stall can fire
19
+ -- stalled_at timestamptz when the current stall began
20
+ -- window_semantics varchar(40) 'future_only' | 'includes_in_progress'
21
+ -- mutation_policy varchar(20) 'same_id' | 'new_id' (feeds #958's
22
+ -- idempotency key template choice)
23
+ --
24
+ -- Hand-written following the 0044-0055 precedent (snapshot drift keeps
25
+ -- drizzle-kit generate interactive). Additive + idempotent. No explicit
26
+ -- BEGIN/COMMIT — the boot migrator executes this file on a pooled postgres-js
27
+ -- connection, which rejects raw transaction control.
28
+
29
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "expected_interval_seconds" integer;
30
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "last_heartbeat_at" timestamp with time zone;
31
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "heartbeat_count" integer DEFAULT 0 NOT NULL;
32
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "liveness_status" varchar(20);
33
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "liveness_armed_at" timestamp with time zone;
34
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "stalled_at" timestamp with time zone;
35
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "window_semantics" varchar(40);
36
+ ALTER TABLE "webhook_sources" ADD COLUMN IF NOT EXISTS "mutation_policy" varchar(20);
37
+
38
+ -- The liveness sweeper's scan: enabled sources with a declared cadence.
39
+ CREATE INDEX IF NOT EXISTS "webhook_sources_supervised_idx"
40
+ ON "webhook_sources" ("enabled") WHERE "expected_interval_seconds" IS NOT NULL;
@@ -400,6 +400,13 @@
400
400
  "when": 1785600000000,
401
401
  "tag": "0056_event_schema_registry",
402
402
  "breakpoints": true
403
+ },
404
+ {
405
+ "idx": 57,
406
+ "version": "7",
407
+ "when": 1785700000000,
408
+ "tag": "0057_connector_lifecycle_contract",
409
+ "breakpoints": true
403
410
  }
404
411
  ]
405
412
  }
@@ -1 +1 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/commands/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,UAAU,sBAAsB;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAcD;;;;;GAKG;AACH,iBAAe,sBAAsB,CACnC,OAAO,EAAE,sBAAsB,EAC/B,SAAS,GAAE,MAAM,OAAO,CAAC,MAAM,CAAuB,GACrD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqC7B;AAED,6FAA6F;AAC7F,iBAAS,oBAAoB,CAAC,OAAO,EAAE;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,0BAA0B,GAAG,SAAS,CAczC;AAED;;;;;GAKG;AACH,iBAAS,6BAA6B,CACpC,eAAe,EAAE,0BAA0B,GAAG,SAAS,EACvD,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,IAAI,CAQN;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CA2P/C;AAED,wDAAwD;AACxD,eAAO,MAAM,WAAW;;;;CAAkF,CAAC"}
1
+ {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../../src/commands/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,UAAU,sBAAsB;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAcD;;;;;GAKG;AACH,iBAAe,sBAAsB,CACnC,OAAO,EAAE,sBAAsB,EAC/B,SAAS,GAAE,MAAM,OAAO,CAAC,MAAM,CAAuB,GACrD,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAqC7B;AAED,6FAA6F;AAC7F,iBAAS,oBAAoB,CAAC,OAAO,EAAE;IACrC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,0BAA0B,GAAG,SAAS,CAczC;AAED;;;;;GAKG;AACH,iBAAS,6BAA6B,CACpC,eAAe,EAAE,0BAA0B,GAAG,SAAS,EACvD,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,IAAI,CAQN;AA4DD,wBAAgB,qBAAqB,IAAI,OAAO,CA6S/C;AAED,wDAAwD;AACxD,eAAO,MAAM,WAAW;;;;CAAkF,CAAC"}
package/dist/index.js CHANGED
@@ -30739,7 +30739,24 @@ var init_registry = __esm(() => {
30739
30739
  component: exports_external.string(),
30740
30740
  reason: exports_external.string(),
30741
30741
  severity: exports_external.enum(["warning", "critical"])
30742
- }), { description: "System health degradation detected" })
30742
+ }), { description: "System health degradation detected" }),
30743
+ connectorStalled: createEventSchema("system.connector.stalled", exports_external.object({
30744
+ sourceId: exports_external.string(),
30745
+ sourceName: exports_external.string(),
30746
+ expectedIntervalSeconds: exports_external.number(),
30747
+ lastReceivedAt: exports_external.number().nullable(),
30748
+ lastHeartbeatAt: exports_external.number().nullable(),
30749
+ silentForSeconds: exports_external.number(),
30750
+ stalledAt: exports_external.number()
30751
+ }), { description: "A supervised connector went silent beyond its declared cadence" }),
30752
+ connectorRecovered: createEventSchema("system.connector.recovered", exports_external.object({
30753
+ sourceId: exports_external.string(),
30754
+ sourceName: exports_external.string(),
30755
+ expectedIntervalSeconds: exports_external.number(),
30756
+ stalledForSeconds: exports_external.number(),
30757
+ recoveredBy: exports_external.enum(["event", "heartbeat", "rearmed"]),
30758
+ recoveredAt: exports_external.number()
30759
+ }), { description: "A stalled connector signalled again (event, heartbeat, or re-declared cadence)" })
30743
30760
  };
30744
30761
  CustomEventSchemas = {
30745
30762
  chatUnreadUpdated: createEventSchema("custom.chat.unread-updated", exports_external.object({
@@ -47118,6 +47135,113 @@ var init_automations = __esm(() => {
47118
47135
  init_sweeper();
47119
47136
  });
47120
47137
 
47138
+ // ../core/src/connectors/liveness.ts
47139
+ function latestConnectorSignal(row) {
47140
+ let best = { at: row.createdAt, kind: "rearmed" };
47141
+ const candidates = [
47142
+ { at: row.livenessArmedAt, kind: "rearmed" },
47143
+ { at: row.lastReceivedAt, kind: "event" },
47144
+ { at: row.lastHeartbeatAt, kind: "heartbeat" }
47145
+ ];
47146
+ for (const candidate of candidates) {
47147
+ if (candidate.at && candidate.at.getTime() >= best.at.getTime()) {
47148
+ best = { at: candidate.at, kind: candidate.kind };
47149
+ }
47150
+ }
47151
+ return best;
47152
+ }
47153
+ async function sweepConnectorLiveness(deps) {
47154
+ const clock = deps.now ?? (() => new Date);
47155
+ const now = clock();
47156
+ const rows = await deps.repo.findSupervised();
47157
+ const stats = { scanned: rows.length, stalled: 0, recovered: 0, errors: 0 };
47158
+ for (const row of rows) {
47159
+ try {
47160
+ await processRow2(row, now, deps, stats);
47161
+ } catch (err2) {
47162
+ stats.errors += 1;
47163
+ deps.logger.error("connector liveness: failed to process source", {
47164
+ sourceId: row.id,
47165
+ sourceName: row.name,
47166
+ error: err2 instanceof Error ? err2.message : String(err2)
47167
+ });
47168
+ }
47169
+ }
47170
+ if (stats.stalled > 0 || stats.recovered > 0) {
47171
+ deps.logger.info("connector liveness sweep tick", { ...stats });
47172
+ }
47173
+ return stats;
47174
+ }
47175
+ async function processRow2(row, now, deps, stats) {
47176
+ const signal = latestConnectorSignal(row);
47177
+ const silentMs = Math.max(0, now.getTime() - signal.at.getTime());
47178
+ const overdue = silentMs > row.expectedIntervalSeconds * 1000;
47179
+ const silentForSeconds = Math.round(silentMs / 1000);
47180
+ if (overdue) {
47181
+ if (row.livenessStatus === "stalled")
47182
+ return;
47183
+ if (!await deps.repo.markStalled(row.id, now))
47184
+ return;
47185
+ const payload = {
47186
+ sourceId: row.id,
47187
+ sourceName: row.name,
47188
+ expectedIntervalSeconds: row.expectedIntervalSeconds,
47189
+ lastReceivedAt: row.lastReceivedAt?.getTime() ?? null,
47190
+ lastHeartbeatAt: row.lastHeartbeatAt?.getTime() ?? null,
47191
+ silentForSeconds,
47192
+ stalledAt: now.getTime()
47193
+ };
47194
+ const eventId = await publishTransition(deps, "system.connector.stalled", { ...payload }, row);
47195
+ stats.stalled += 1;
47196
+ await runHook(deps.logger, "onStalled", row, () => deps.onStalled?.(row, payload, eventId));
47197
+ return;
47198
+ }
47199
+ if (row.livenessStatus === "stalled") {
47200
+ if (!await deps.repo.markRecovered(row.id, now))
47201
+ return;
47202
+ const stalledSince = row.stalledAt ?? signal.at;
47203
+ const payload = {
47204
+ sourceId: row.id,
47205
+ sourceName: row.name,
47206
+ expectedIntervalSeconds: row.expectedIntervalSeconds,
47207
+ stalledForSeconds: Math.max(0, Math.round((now.getTime() - stalledSince.getTime()) / 1000)),
47208
+ recoveredBy: signal.kind,
47209
+ recoveredAt: now.getTime()
47210
+ };
47211
+ await publishTransition(deps, "system.connector.recovered", { ...payload }, row);
47212
+ stats.recovered += 1;
47213
+ await runHook(deps.logger, "onRecovered", row, () => deps.onRecovered?.(row, payload));
47214
+ }
47215
+ }
47216
+ async function publishTransition(deps, type, payload, row) {
47217
+ if (!deps.eventBus) {
47218
+ deps.logger.warn("connector liveness: no event bus, transition persisted without event", {
47219
+ type,
47220
+ sourceName: row.name
47221
+ });
47222
+ return null;
47223
+ }
47224
+ const result = await deps.eventBus.publishGeneric(type, payload, {
47225
+ source: "connector-liveness",
47226
+ ...row.tenantId ? { tenantId: row.tenantId } : {}
47227
+ });
47228
+ return result.id;
47229
+ }
47230
+ async function runHook(logger5, name, row, hook) {
47231
+ try {
47232
+ await hook();
47233
+ } catch (err2) {
47234
+ logger5.error(`connector liveness: ${name} hook failed`, {
47235
+ sourceId: row.id,
47236
+ sourceName: row.name,
47237
+ error: err2 instanceof Error ? err2.message : String(err2)
47238
+ });
47239
+ }
47240
+ }
47241
+
47242
+ // ../core/src/connectors/index.ts
47243
+ var init_connectors = () => {};
47244
+
47121
47245
  // ../core/src/sessions/reset.ts
47122
47246
  class InMemorySessionActivityStore {
47123
47247
  activities = new Map;
@@ -75220,6 +75344,7 @@ __export(exports_src, {
75220
75344
  tenantLabelBucketCount: () => tenantLabelBucketCount,
75221
75345
  tenantEventsProcessed: () => tenantEventsProcessed,
75222
75346
  sweepFollowUps: () => sweepFollowUps,
75347
+ sweepConnectorLiveness: () => sweepConnectorLiveness,
75223
75348
  substituteTemplateObject: () => substituteTemplateObject,
75224
75349
  substituteTemplate: () => substituteTemplate,
75225
75350
  stampsEqual: () => stampsEqual,
@@ -75282,6 +75407,7 @@ __export(exports_src, {
75282
75407
  natsConnectionStatus: () => natsConnectionStatus,
75283
75408
  matchesPattern: () => matchesPattern,
75284
75409
  markdownToWhatsApp: () => markdownToWhatsApp,
75410
+ latestConnectorSignal: () => latestConnectorSignal,
75285
75411
  jsonEquals: () => jsonEquals,
75286
75412
  isValidUuid: () => isValidUuid,
75287
75413
  isValidJsonSchema: () => isValidJsonSchema,
@@ -75607,6 +75733,7 @@ var init_src2 = __esm(() => {
75607
75733
  init_observability();
75608
75734
  init_secrets();
75609
75735
  init_automations();
75736
+ init_connectors();
75610
75737
  init_reset();
75611
75738
  init_providers();
75612
75739
  init_cache();
@@ -80968,6 +81095,9 @@ __export(exports_schema, {
80968
81095
  conversations: () => conversations,
80969
81096
  contentTypes: () => contentTypes,
80970
81097
  consumerOffsets: () => consumerOffsets,
81098
+ connectorWindowSemanticsValues: () => connectorWindowSemanticsValues,
81099
+ connectorMutationPolicies: () => connectorMutationPolicies,
81100
+ connectorLivenessStatuses: () => connectorLivenessStatuses,
80971
81101
  conditionOperators: () => conditionOperators,
80972
81102
  closeContactOutcomes: () => closeContactOutcomes,
80973
81103
  closeContactLogs: () => closeContactLogs,
@@ -81016,7 +81146,7 @@ __export(exports_schema, {
81016
81146
  accessRules: () => accessRules,
81017
81147
  accessModes: () => accessModes
81018
81148
  });
81019
- var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, supersedeMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, scheduledMessageDeliveryModes, scheduledMessageStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, whatsappTemplates, whatsappFlowKeys, scheduledMessages, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages2, omniEvents, handoffLogs, closeContactOutcomes, closeContactLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSignatureAlgorithms, webhookSources, eventSchemas, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts, tenantStatuses, principalTypes, principalStatuses, tenantRoles, membershipStatuses, credentialClasses, authCredentialStatuses, platformApiKeyStatuses, tenants, principals, tenantMemberships, tenantRolePolicies, platformApiKeys, tenantKeyLineage, authCredentials, tenantAuditLogs, platformAuditLogs, platformProviderCatalog, tenantProviderConfig, platformSettings, tenantSettings, platformSettingChangeHistory, tenantSettingChangeHistory, platformPluginStorage, tenantPluginStorage, platformPayloadStorageConfig, tenantPayloadStorageOverrides, tenantMigrationLedger, tenantMigrationLedgerHistory;
81149
+ var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, supersedeMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, scheduledMessageDeliveryModes, scheduledMessageStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, whatsappTemplates, whatsappFlowKeys, scheduledMessages, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages2, omniEvents, handoffLogs, closeContactOutcomes, closeContactLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSignatureAlgorithms, connectorLivenessStatuses, connectorWindowSemanticsValues, connectorMutationPolicies, webhookSources, eventSchemas, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts, tenantStatuses, principalTypes, principalStatuses, tenantRoles, membershipStatuses, credentialClasses, authCredentialStatuses, platformApiKeyStatuses, tenants, principals, tenantMemberships, tenantRolePolicies, platformApiKeys, tenantKeyLineage, authCredentials, tenantAuditLogs, platformAuditLogs, platformProviderCatalog, tenantProviderConfig, platformSettings, tenantSettings, platformSettingChangeHistory, tenantSettingChangeHistory, platformPluginStorage, tenantPluginStorage, platformPayloadStorageConfig, tenantPayloadStorageOverrides, tenantMigrationLedger, tenantMigrationLedgerHistory;
81020
81150
  var init_schema2 = __esm(() => {
81021
81151
  init_events();
81022
81152
  init_types5();
@@ -82229,6 +82359,9 @@ var init_schema2 = __esm(() => {
82229
82359
  eventStageIdx: index("event_payloads_event_stage_idx").on(table3.eventId, table3.stage)
82230
82360
  }));
82231
82361
  webhookSignatureAlgorithms = ["hmac-sha256", "hmac-sha1", "token-match"];
82362
+ connectorLivenessStatuses = ["healthy", "stalled"];
82363
+ connectorWindowSemanticsValues = ["future_only", "includes_in_progress"];
82364
+ connectorMutationPolicies = ["same_id", "new_id"];
82232
82365
  webhookSources = pgTable("webhook_sources", {
82233
82366
  id: uuid("id").primaryKey().defaultRandom(),
82234
82367
  name: varchar("name", { length: 100 }).notNull().unique(),
@@ -82240,6 +82373,14 @@ var init_schema2 = __esm(() => {
82240
82373
  enabled: boolean2("enabled").notNull().default(true),
82241
82374
  lastReceivedAt: timestamp("last_received_at", { withTimezone: true }),
82242
82375
  totalReceived: integer("total_received").notNull().default(0),
82376
+ expectedIntervalSeconds: integer("expected_interval_seconds"),
82377
+ lastHeartbeatAt: timestamp("last_heartbeat_at", { withTimezone: true }),
82378
+ heartbeatCount: integer("heartbeat_count").notNull().default(0),
82379
+ livenessStatus: varchar("liveness_status", { length: 20 }).$type(),
82380
+ livenessArmedAt: timestamp("liveness_armed_at", { withTimezone: true }),
82381
+ stalledAt: timestamp("stalled_at", { withTimezone: true }),
82382
+ windowSemantics: varchar("window_semantics", { length: 40 }).$type(),
82383
+ mutationPolicy: varchar("mutation_policy", { length: 20 }).$type(),
82243
82384
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
82244
82385
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
82245
82386
  tenantId: uuid("tenant_id").references(() => tenants.id, { onDelete: "restrict" })
@@ -82247,7 +82388,8 @@ var init_schema2 = __esm(() => {
82247
82388
  tenantIdx: index("webhook_sources_tenant_idx").on(table3.tenantId),
82248
82389
  tenantNameUq: uniqueIndex("webhook_sources_tenant_name_uq").on(table3.tenantId, table3.name).where(sql`${table3.tenantId} IS NOT NULL`),
82249
82390
  nameIdx: uniqueIndex("webhook_sources_name_idx").on(table3.name),
82250
- enabledIdx: index("webhook_sources_enabled_idx").on(table3.enabled)
82391
+ enabledIdx: index("webhook_sources_enabled_idx").on(table3.enabled),
82392
+ supervisedIdx: index("webhook_sources_supervised_idx").on(table3.enabled).where(sql`${table3.expectedIntervalSeconds} IS NOT NULL`)
82251
82393
  }));
82252
82394
  eventSchemas = pgTable("event_schemas", {
82253
82395
  id: uuid("id").primaryKey().defaultRandom(),
@@ -135302,6 +135444,13 @@ function createOmniClient(config) {
135302
135444
  const { data, error, response } = await client.POST("/events/trigger", { body });
135303
135445
  throwIfError(response, error);
135304
135446
  return data ?? { eventId: "", eventType: body.eventType };
135447
+ },
135448
+ async heartbeat(source) {
135449
+ const { data, error, response } = await client.POST("/webhooks/{source}/heartbeat", {
135450
+ params: { path: { source } }
135451
+ });
135452
+ throwIfError(response, error);
135453
+ return data;
135305
135454
  }
135306
135455
  },
135307
135456
  payloads: {
@@ -136109,7 +136258,7 @@ import { fileURLToPath } from "url";
136109
136258
  // package.json
136110
136259
  var package_default = {
136111
136260
  name: "@automagik/omni",
136112
- version: "2.260906.2",
136261
+ version: "2.260906.3",
136113
136262
  repository: {
136114
136263
  type: "git",
136115
136264
  url: "git+https://github.com/automagik-dev/omni.git",
@@ -150609,6 +150758,53 @@ function assertPairedSignatureOnCreate(signatureConfig, signatureSecret) {
150609
150758
  }
150610
150759
  throw new Error(`--signature-algorithm and --signature-header require a signature secret (${SECRET_SOURCE_FLAGS})`);
150611
150760
  }
150761
+ var WINDOW_SEMANTICS = ["future_only", "includes_in_progress"];
150762
+ var MUTATION_POLICIES = ["same_id", "new_id"];
150763
+ function parseExpectedInterval(raw2) {
150764
+ if (raw2 === undefined)
150765
+ return;
150766
+ const seconds = Number(raw2);
150767
+ if (!Number.isInteger(seconds) || seconds < 1) {
150768
+ error("--expected-interval must be a positive integer number of seconds");
150769
+ }
150770
+ return seconds;
150771
+ }
150772
+ function parseWindowSemantics(raw2) {
150773
+ if (raw2 === undefined)
150774
+ return;
150775
+ const value = WINDOW_SEMANTICS.find((v4) => v4 === raw2);
150776
+ if (!value) {
150777
+ error(`Invalid --window-semantics; expected one of: ${WINDOW_SEMANTICS.join(", ")}`);
150778
+ }
150779
+ return value;
150780
+ }
150781
+ function parseMutationPolicy(raw2) {
150782
+ if (raw2 === undefined)
150783
+ return;
150784
+ const value = MUTATION_POLICIES.find((v4) => v4 === raw2);
150785
+ if (!value) {
150786
+ error(`Invalid --mutation-policy; expected one of: ${MUTATION_POLICIES.join(", ")}`);
150787
+ }
150788
+ return value;
150789
+ }
150790
+ function buildLifecycleUpdates(options3) {
150791
+ if (options3.clearCadence && options3.expectedInterval !== undefined) {
150792
+ error("Use only one of --expected-interval and --clear-cadence");
150793
+ }
150794
+ const updates = {};
150795
+ if (options3.clearCadence)
150796
+ updates.expectedIntervalSeconds = null;
150797
+ const expectedIntervalSeconds = parseExpectedInterval(options3.expectedInterval);
150798
+ if (expectedIntervalSeconds !== undefined)
150799
+ updates.expectedIntervalSeconds = expectedIntervalSeconds;
150800
+ const windowSemantics = parseWindowSemantics(options3.windowSemantics);
150801
+ if (windowSemantics !== undefined)
150802
+ updates.windowSemantics = windowSemantics;
150803
+ const mutationPolicy = parseMutationPolicy(options3.mutationPolicy);
150804
+ if (mutationPolicy !== undefined)
150805
+ updates.mutationPolicy = mutationPolicy;
150806
+ return updates;
150807
+ }
150612
150808
  function createWebhooksCommand() {
150613
150809
  const webhooks = new Command("webhooks").description("Manage webhook sources");
150614
150810
  webhooks.command("list").description("List webhook sources").option("--enabled", "Show only enabled sources").option("--disabled", "Show only disabled sources").action(async (options3) => {
@@ -150626,6 +150822,8 @@ function createWebhooksCommand() {
150626
150822
  id: w.id,
150627
150823
  name: w.name,
150628
150824
  enabled: w.enabled ? "yes" : "no",
150825
+ health: w.livenessStatus ?? "-",
150826
+ cadence: w.expectedIntervalSeconds != null ? `${w.expectedIntervalSeconds}s` : "-",
150629
150827
  createdAt: w.createdAt
150630
150828
  }));
150631
150829
  list(items, { emptyMessage: "No webhook sources found." });
@@ -150645,7 +150843,7 @@ function createWebhooksCommand() {
150645
150843
  error(`Failed to get webhook source: ${message2}`);
150646
150844
  }
150647
150845
  });
150648
- webhooks.command("create").description("Create a webhook source").requiredOption("--name <name>", "Webhook source name").option("--description <desc>", "Description").option("--disabled", "Create in disabled state").option("--headers <json>", `Expected headers as JSON (e.g., '{"X-Secret": true}')`).option("--signature-algorithm <alg>", "Signature verification: hmac-sha256, hmac-sha1, or token-match").option("--signature-header <name>", "Header carrying the signature/token (e.g., X-Hub-Signature-256)").option("--signature-prefix <prefix>", "Prefix before the hex digest (e.g., 'sha256=')").option("--signature-secret <secret>", "Shared secret for signature verification (write-only). Visible in shell history and process lists \u2014 prefer --signature-secret-env or --signature-secret-stdin").option("--signature-secret-env <VAR>", "Read the shared secret from environment variable VAR").option("--signature-secret-stdin", "Read the shared secret from stdin (trailing newline stripped)").action(async (options3) => {
150846
+ webhooks.command("create").description("Create a webhook source").requiredOption("--name <name>", "Webhook source name").option("--description <desc>", "Description").option("--disabled", "Create in disabled state").option("--headers <json>", `Expected headers as JSON (e.g., '{"X-Secret": true}')`).option("--signature-algorithm <alg>", "Signature verification: hmac-sha256, hmac-sha1, or token-match").option("--signature-header <name>", "Header carrying the signature/token (e.g., X-Hub-Signature-256)").option("--signature-prefix <prefix>", "Prefix before the hex digest (e.g., 'sha256=')").option("--signature-secret <secret>", "Shared secret for signature verification (write-only). Visible in shell history and process lists \u2014 prefer --signature-secret-env or --signature-secret-stdin").option("--signature-secret-env <VAR>", "Read the shared secret from environment variable VAR").option("--signature-secret-stdin", "Read the shared secret from stdin (trailing newline stripped)").option("--expected-interval <seconds>", "Declared cadence: >=1 event or heartbeat per N seconds. Arms liveness supervision (#961)").option("--window-semantics <value>", `Declared window semantics: ${WINDOW_SEMANTICS.join(" or ")}`).option("--mutation-policy <value>", `Declared mutation re-emit policy: ${MUTATION_POLICIES.join(" or ")}`).action(async (options3) => {
150649
150847
  const client = getClient();
150650
150848
  try {
150651
150849
  let expectedHeaders;
@@ -150665,7 +150863,10 @@ function createWebhooksCommand() {
150665
150863
  enabled: !options3.disabled,
150666
150864
  expectedHeaders,
150667
150865
  signatureConfig,
150668
- signatureSecret
150866
+ signatureSecret,
150867
+ expectedIntervalSeconds: parseExpectedInterval(options3.expectedInterval),
150868
+ windowSemantics: parseWindowSemantics(options3.windowSemantics),
150869
+ mutationPolicy: parseMutationPolicy(options3.mutationPolicy)
150669
150870
  });
150670
150871
  const details = {
150671
150872
  id: source.id,
@@ -150681,11 +150882,11 @@ function createWebhooksCommand() {
150681
150882
  error(`Failed to create webhook source: ${message2}`);
150682
150883
  }
150683
150884
  });
150684
- webhooks.command("update <id>").description("Update a webhook source").option("--name <name>", "New name").option("--description <desc>", "New description").option("--enable", "Enable the webhook").option("--disable", "Disable the webhook").option("--signature-algorithm <alg>", "Signature verification: hmac-sha256, hmac-sha1, or token-match").option("--signature-header <name>", "Header carrying the signature/token (e.g., X-Hub-Signature-256)").option("--signature-prefix <prefix>", "Prefix before the hex digest (e.g., 'sha256=')").option("--signature-secret <secret>", "Shared secret for signature verification (write-only). Visible in shell history and process lists \u2014 prefer --signature-secret-env or --signature-secret-stdin").option("--signature-secret-env <VAR>", "Read the shared secret from environment variable VAR").option("--signature-secret-stdin", "Read the shared secret from stdin (trailing newline stripped)").option("--clear-signature", "Remove the signature config and stored secret").action(async (id, options3) => {
150885
+ webhooks.command("update <id>").description("Update a webhook source").option("--name <name>", "New name").option("--description <desc>", "New description").option("--enable", "Enable the webhook").option("--disable", "Disable the webhook").option("--signature-algorithm <alg>", "Signature verification: hmac-sha256, hmac-sha1, or token-match").option("--signature-header <name>", "Header carrying the signature/token (e.g., X-Hub-Signature-256)").option("--signature-prefix <prefix>", "Prefix before the hex digest (e.g., 'sha256=')").option("--signature-secret <secret>", "Shared secret for signature verification (write-only). Visible in shell history and process lists \u2014 prefer --signature-secret-env or --signature-secret-stdin").option("--signature-secret-env <VAR>", "Read the shared secret from environment variable VAR").option("--signature-secret-stdin", "Read the shared secret from stdin (trailing newline stripped)").option("--clear-signature", "Remove the signature config and stored secret").option("--expected-interval <seconds>", "Declared cadence: >=1 event or heartbeat per N seconds. (Re)arms liveness supervision (#961)").option("--clear-cadence", "Remove the declared cadence (disarms liveness supervision)").option("--window-semantics <value>", `Declared window semantics: ${WINDOW_SEMANTICS.join(" or ")}`).option("--mutation-policy <value>", `Declared mutation re-emit policy: ${MUTATION_POLICIES.join(" or ")}`).action(async (id, options3) => {
150685
150886
  const resolvedId = await resolveWebhookId(id);
150686
150887
  const client = getClient();
150687
150888
  try {
150688
- const updates = {};
150889
+ const updates = buildLifecycleUpdates(options3);
150689
150890
  if (options3.name)
150690
150891
  updates.name = options3.name;
150691
150892
  if (options3.description)
@@ -150751,6 +150952,20 @@ function createWebhooksCommand() {
150751
150952
  error(`Failed to trigger event: ${message2}`);
150752
150953
  }
150753
150954
  });
150955
+ webhooks.command("heartbeat <source>").description('Record a connector heartbeat ("ran, zero events found") \u2014 resets the liveness window (#961)').action(async (source) => {
150956
+ const client = getClient();
150957
+ try {
150958
+ const result = await client.webhooks.heartbeat(source);
150959
+ success(`Heartbeat recorded for '${result.source}'`, {
150960
+ heartbeatAt: result.heartbeatAt,
150961
+ livenessStatus: result.livenessStatus ?? "unsupervised",
150962
+ expectedIntervalSeconds: result.expectedIntervalSeconds
150963
+ });
150964
+ } catch (err2) {
150965
+ const message2 = err2 instanceof Error ? err2.message : "Unknown error";
150966
+ error(`Failed to record heartbeat: ${message2}`);
150967
+ }
150968
+ });
150754
150969
  return webhooks;
150755
150970
  }
150756
150971
 
@@ -663,7 +663,22 @@ export interface CreateWebhookSourceBody {
663
663
  signatureSecret?: string | null;
664
664
  /** Defaults to true server-side */
665
665
  enabled?: boolean;
666
+ /**
667
+ * Connector lifecycle contract (#961): declared cadence — the connector
668
+ * promises >=1 event or heartbeat per N seconds (1s-30d). Declaring it arms
669
+ * liveness supervision; null disarms it.
670
+ */
671
+ expectedIntervalSeconds?: number | null;
672
+ /** Declared window semantics; null = undeclared. */
673
+ windowSemantics?: 'future_only' | 'includes_in_progress' | null;
674
+ /** Declared upstream-mutation re-emit policy; null = undeclared. */
675
+ mutationPolicy?: 'same_id' | 'new_id' | null;
666
676
  }
677
+ /**
678
+ * Response of the connector heartbeat verb (#961).
679
+ * Derived from the generated OpenAPI component so it cannot drift from the API.
680
+ */
681
+ export type WebhookHeartbeatResponse = components['schemas']['WebhookHeartbeatResponse'];
667
682
  /**
668
683
  * Body for triggering a custom event
669
684
  */
@@ -1869,6 +1884,12 @@ export declare function createOmniClient(config: OmniClientConfig): {
1869
1884
  eventId: string;
1870
1885
  eventType: string;
1871
1886
  }>;
1887
+ /**
1888
+ * Record a connector heartbeat: "I ran, zero events found" (#961).
1889
+ * Resets the source's liveness window without creating a journal event.
1890
+ * Keyed on the source NAME (like the receiver), not the id.
1891
+ */
1892
+ heartbeat(source: string): Promise<WebhookHeartbeatResponse>;
1872
1893
  };
1873
1894
  /**
1874
1895
  * Payload management