@automagik/omni 2.260423.3 → 2.260423.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.
@@ -0,0 +1,45 @@
1
+ -- Issue #407: reconcile gupshup column drift.
2
+ --
3
+ -- Migration 0018_supreme_puma renamed three columns on "instances":
4
+ -- gupshup_api_key -> gupshup_callback_url
5
+ -- gupshup_app_name -> gupshup_auth_token
6
+ -- gupshup_source_phone -> gupshup_event_id
7
+ --
8
+ -- On at least one deployed DB the migration was marked applied in
9
+ -- drizzle.__drizzle_migrations but the rename never executed, leaving the
10
+ -- live table with the old column names. Because RENAME is not idempotent,
11
+ -- replaying 0018 would fail. This migration is safe to rerun: it adds the
12
+ -- new columns if missing, copies any surviving data from old columns, and
13
+ -- then drops the old columns if present.
14
+
15
+ ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "gupshup_callback_url" text;
16
+ ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "gupshup_auth_token" text;
17
+ ALTER TABLE "instances" ADD COLUMN IF NOT EXISTS "gupshup_event_id" varchar(255);
18
+
19
+ DO $$
20
+ BEGIN
21
+ IF EXISTS (
22
+ SELECT 1 FROM information_schema.columns
23
+ WHERE table_schema = 'public' AND table_name = 'instances' AND column_name = 'gupshup_api_key'
24
+ ) THEN
25
+ EXECUTE 'UPDATE "instances" SET "gupshup_callback_url" = "gupshup_api_key" WHERE "gupshup_callback_url" IS NULL AND "gupshup_api_key" IS NOT NULL';
26
+ END IF;
27
+
28
+ IF EXISTS (
29
+ SELECT 1 FROM information_schema.columns
30
+ WHERE table_schema = 'public' AND table_name = 'instances' AND column_name = 'gupshup_app_name'
31
+ ) THEN
32
+ EXECUTE 'UPDATE "instances" SET "gupshup_auth_token" = "gupshup_app_name" WHERE "gupshup_auth_token" IS NULL AND "gupshup_app_name" IS NOT NULL';
33
+ END IF;
34
+
35
+ IF EXISTS (
36
+ SELECT 1 FROM information_schema.columns
37
+ WHERE table_schema = 'public' AND table_name = 'instances' AND column_name = 'gupshup_source_phone'
38
+ ) THEN
39
+ EXECUTE 'UPDATE "instances" SET "gupshup_event_id" = "gupshup_source_phone" WHERE "gupshup_event_id" IS NULL AND "gupshup_source_phone" IS NOT NULL';
40
+ END IF;
41
+ END $$;
42
+
43
+ ALTER TABLE "instances" DROP COLUMN IF EXISTS "gupshup_api_key";
44
+ ALTER TABLE "instances" DROP COLUMN IF EXISTS "gupshup_app_name";
45
+ ALTER TABLE "instances" DROP COLUMN IF EXISTS "gupshup_source_phone";
@@ -218,6 +218,13 @@
218
218
  "when": 1777300000000,
219
219
  "tag": "0030_twilio_whatsapp_channel",
220
220
  "breakpoints": true
221
+ },
222
+ {
223
+ "idx": 31,
224
+ "version": "7",
225
+ "when": 1777400000000,
226
+ "tag": "0031_reconcile_gupshup_columns",
227
+ "breakpoints": true
221
228
  }
222
229
  ]
223
230
  }
package/dist/index.js CHANGED
@@ -62070,10 +62070,14 @@ var init_migrate = __esm(() => {
62070
62070
  if (false) {}
62071
62071
  });
62072
62072
 
62073
+ // ../db/src/verify-schema.ts
62074
+ var init_verify_schema = () => {};
62075
+
62073
62076
  // ../db/src/index.ts
62074
62077
  var init_src3 = __esm(() => {
62075
62078
  init_client3();
62076
62079
  init_migrate();
62080
+ init_verify_schema();
62077
62081
  init_schema2();
62078
62082
  });
62079
62083
 
@@ -113839,7 +113843,7 @@ import { fileURLToPath } from "url";
113839
113843
  // package.json
113840
113844
  var package_default = {
113841
113845
  name: "@automagik/omni",
113842
- version: "2.260423.3",
113846
+ version: "2.260423.5",
113843
113847
  description: "LLM-optimized CLI for Omni",
113844
113848
  type: "module",
113845
113849
  bin: {
@@ -224464,7 +224464,7 @@ var init_sentry_scrub = __esm(() => {
224464
224464
  var require_package8 = __commonJS((exports, module) => {
224465
224465
  module.exports = {
224466
224466
  name: "@omni/api",
224467
- version: "2.260423.3",
224467
+ version: "2.260423.5",
224468
224468
  type: "module",
224469
224469
  exports: {
224470
224470
  ".": {
@@ -233528,10 +233528,68 @@ var init_migrate = __esm(() => {
233528
233528
  if (false) {}
233529
233529
  });
233530
233530
 
233531
+ // ../db/src/verify-schema.ts
233532
+ async function verifyCriticalColumns(db2, expectations) {
233533
+ if (expectations.length === 0) {
233534
+ return { ok: true, drift: [] };
233535
+ }
233536
+ const tables = expectations.map((e) => e.table);
233537
+ const rows = await db2.execute(sql`
233538
+ SELECT table_name, column_name
233539
+ FROM information_schema.columns
233540
+ WHERE table_schema = 'public'
233541
+ AND table_name IN (${sql.join(tables.map((t) => sql`${t}`), sql`, `)})
233542
+ `);
233543
+ const liveColumns = new Map;
233544
+ for (const row of rows) {
233545
+ const set = liveColumns.get(row.table_name) ?? new Set;
233546
+ set.add(row.column_name);
233547
+ liveColumns.set(row.table_name, set);
233548
+ }
233549
+ const drift = [];
233550
+ for (const expectation of expectations) {
233551
+ const live = liveColumns.get(expectation.table) ?? new Set;
233552
+ const missing = expectation.columns.filter((col) => !live.has(col));
233553
+ if (missing.length > 0) {
233554
+ drift.push({ table: expectation.table, missing });
233555
+ }
233556
+ }
233557
+ return { ok: drift.length === 0, drift };
233558
+ }
233559
+ function formatDriftReport(report) {
233560
+ if (report.ok)
233561
+ return "Schema drift check passed.";
233562
+ const lines = [
233563
+ "Schema drift detected \u2014 live database is missing columns Drizzle expects.",
233564
+ "This usually means drizzle-kit push was used against a migrated database,",
233565
+ "or a migration was marked applied but its SQL did not execute.",
233566
+ ""
233567
+ ];
233568
+ for (const entry of report.drift) {
233569
+ lines.push(` table "${entry.table}" missing columns: ${entry.missing.join(", ")}`);
233570
+ }
233571
+ lines.push("");
233572
+ lines.push("Run `bun run db:verify-drift` locally against the same DATABASE_URL,");
233573
+ lines.push("then apply the reconcile migration (see issue #407).");
233574
+ return lines.join(`
233575
+ `);
233576
+ }
233577
+ var API_CRITICAL_COLUMNS;
233578
+ var init_verify_schema = __esm(() => {
233579
+ init_drizzle_orm();
233580
+ API_CRITICAL_COLUMNS = [
233581
+ {
233582
+ table: "instances",
233583
+ columns: ["gupshup_callback_url", "gupshup_auth_token", "gupshup_event_id"]
233584
+ }
233585
+ ];
233586
+ });
233587
+
233531
233588
  // ../db/src/index.ts
233532
233589
  var init_src5 = __esm(() => {
233533
233590
  init_client5();
233534
233591
  init_migrate();
233592
+ init_verify_schema();
233535
233593
  init_schema2();
233536
233594
  });
233537
233595
 
@@ -278915,11 +278973,11 @@ class BatchJobService {
278915
278973
  if (!mimeType || !this.mediaService.canProcess(mimeType)) {
278916
278974
  return this.failedResult(`MIME type not processable: ${mimeType}`);
278917
278975
  }
278918
- const filePath = await this.resolveFilePath(instanceId, message2, mimeType);
278919
- if (!filePath) {
278920
- return this.failedResult("No media file path available");
278976
+ const resolved = await this.resolveFilePath(instanceId, message2, mimeType);
278977
+ if (!resolved.ok) {
278978
+ return this.failedResult(resolved.reason);
278921
278979
  }
278922
- const fullPath = join19(this.mediaStorage.getBasePath(), filePath);
278980
+ const fullPath = join19(this.mediaStorage.getBasePath(), resolved.path);
278923
278981
  const result = await this.mediaService.process(fullPath, mimeType, {
278924
278982
  language: "pt",
278925
278983
  caption: message2.textContent ?? undefined
@@ -278943,17 +279001,23 @@ class BatchJobService {
278943
279001
  }
278944
279002
  async resolveFilePath(instanceId, message2, mimeType) {
278945
279003
  if (message2.mediaLocalPath) {
278946
- return message2.mediaLocalPath;
279004
+ return { ok: true, path: message2.mediaLocalPath };
278947
279005
  }
278948
279006
  if (!message2.mediaUrl) {
278949
- return null;
279007
+ return { ok: false, reason: "No media_url and no media_local_path on message" };
278950
279008
  }
278951
279009
  try {
278952
279010
  const result = await this.mediaStorage.storeFromUrl(instanceId, message2.id, message2.mediaUrl, mimeType, message2.platformTimestamp ?? undefined);
278953
279011
  await this.mediaStorage.updateMessageLocalPath(message2.id, result.localPath);
278954
- return result.localPath;
278955
- } catch {
278956
- return null;
279012
+ return { ok: true, path: result.localPath };
279013
+ } catch (error2) {
279014
+ const reason = `storeFromUrl failed: ${error2 instanceof Error ? error2.message : String(error2)}`;
279015
+ log76.warn("storeFromUrl failed during batch retrofill", {
279016
+ messageId: message2.id,
279017
+ mediaUrl: message2.mediaUrl,
279018
+ error: reason
279019
+ });
279020
+ return { ok: false, reason };
278957
279021
  }
278958
279022
  }
278959
279023
  async persistProcessingResult(messageId, result, batchJobId) {
@@ -305428,6 +305492,20 @@ async function main() {
305428
305492
  throw error2;
305429
305493
  }
305430
305494
  log111.info("Database migrations complete", { durationMs: Date.now() - migrationStart });
305495
+ try {
305496
+ const driftReport = await verifyCriticalColumns(db3, API_CRITICAL_COLUMNS);
305497
+ if (!driftReport.ok) {
305498
+ log111.error(formatDriftReport(driftReport), { drift: driftReport.drift });
305499
+ await closeDb();
305500
+ await stopEmbeddedPgserve();
305501
+ process.exit(1);
305502
+ }
305503
+ } catch (error2) {
305504
+ log111.error("Schema drift check failed", { error: String(error2) });
305505
+ await closeDb();
305506
+ await stopEmbeddedPgserve();
305507
+ process.exit(1);
305508
+ }
305431
305509
  try {
305432
305510
  const [countRow] = await db3.select({ count: sql`count(*)::int` }).from(instances);
305433
305511
  const rowCount = countRow?.count ?? 0;
@@ -424310,14 +424388,16 @@ var contentExtractors = [
424310
424388
  extract: (m2) => ({
424311
424389
  type: "image",
424312
424390
  caption: m2.imageMessage?.caption ?? undefined,
424313
- mimeType: m2.imageMessage?.mimetype ?? "image/jpeg"
424391
+ mimeType: m2.imageMessage?.mimetype ?? "image/jpeg",
424392
+ mediaUrl: m2.imageMessage?.url ?? undefined
424314
424393
  })
424315
424394
  },
424316
424395
  {
424317
424396
  check: (m2) => !!m2.audioMessage,
424318
424397
  extract: (m2) => ({
424319
424398
  type: "audio",
424320
- mimeType: m2.audioMessage?.mimetype ?? "audio/ogg"
424399
+ mimeType: m2.audioMessage?.mimetype ?? "audio/ogg",
424400
+ mediaUrl: m2.audioMessage?.url ?? undefined
424321
424401
  })
424322
424402
  },
424323
424403
  {
@@ -424325,7 +424405,8 @@ var contentExtractors = [
424325
424405
  extract: (m2) => ({
424326
424406
  type: "video",
424327
424407
  caption: m2.videoMessage?.caption ?? undefined,
424328
- mimeType: m2.videoMessage?.mimetype ?? "video/mp4"
424408
+ mimeType: m2.videoMessage?.mimetype ?? "video/mp4",
424409
+ mediaUrl: m2.videoMessage?.url ?? undefined
424329
424410
  })
424330
424411
  },
424331
424412
  {
@@ -424334,14 +424415,16 @@ var contentExtractors = [
424334
424415
  type: "document",
424335
424416
  filename: m2.documentMessage?.fileName ?? undefined,
424336
424417
  mimeType: m2.documentMessage?.mimetype ?? "application/octet-stream",
424337
- caption: m2.documentMessage?.caption ?? undefined
424418
+ caption: m2.documentMessage?.caption ?? undefined,
424419
+ mediaUrl: m2.documentMessage?.url ?? undefined
424338
424420
  })
424339
424421
  },
424340
424422
  {
424341
424423
  check: (m2) => !!m2.stickerMessage,
424342
424424
  extract: (m2) => ({
424343
424425
  type: "sticker",
424344
- mimeType: m2.stickerMessage?.mimetype ?? "image/webp"
424426
+ mimeType: m2.stickerMessage?.mimetype ?? "image/webp",
424427
+ mediaUrl: m2.stickerMessage?.url ?? undefined
424345
424428
  })
424346
424429
  },
424347
424430
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260423.3",
3
+ "version": "2.260423.5",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {
@@ -39,15 +39,15 @@
39
39
  "qrcode-terminal": "^0.12.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@omni/api": "2.260423.2",
43
- "@omni/channel-discord": "2.260423.2",
44
- "@omni/channel-gupshup": "2.260423.2",
45
- "@omni/channel-sdk": "2.260423.2",
46
- "@omni/channel-slack": "2.260423.2",
47
- "@omni/channel-telegram": "2.260423.2",
48
- "@omni/channel-whatsapp": "2.260423.2",
49
- "@omni/core": "2.260423.2",
50
- "@omni/sdk": "2.260423.2",
42
+ "@omni/api": "2.260423.4",
43
+ "@omni/channel-discord": "2.260423.4",
44
+ "@omni/channel-gupshup": "2.260423.4",
45
+ "@omni/channel-sdk": "2.260423.4",
46
+ "@omni/channel-slack": "2.260423.4",
47
+ "@omni/channel-telegram": "2.260423.4",
48
+ "@omni/channel-whatsapp": "2.260423.4",
49
+ "@omni/core": "2.260423.4",
50
+ "@omni/sdk": "2.260423.4",
51
51
  "@types/node": "^22.10.3",
52
52
  "@types/qrcode-terminal": "^0.12.2",
53
53
  "typescript": "^5.7.3"