@automagik/omni 2.260430.16 → 2.260501.2

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.
@@ -38,6 +38,7 @@
38
38
  */
39
39
  import { Command } from 'commander';
40
40
  import { type Config, type ServerConfig } from '../config.js';
41
+ import { type EmbeddedDumpResult } from '../lib/canonical-pgserve.js';
41
42
  /** Severity levels reported by each check. */
42
43
  export type CheckLevel = 'OK' | 'WARN' | 'FAIL';
43
44
  /** Identifier used in tests and --json output. */
@@ -141,6 +142,24 @@ export interface DoctorDeps {
141
142
  * failure. Stubbed in tests.
142
143
  */
143
144
  setupCanonicalPgserve: () => Promise<string | null>;
145
+ /**
146
+ * `pg_dump` the embedded omni DB → gzip → snapshot file. Called BEFORE
147
+ * the caller stops omni-api so the embedded pgserve is still live for
148
+ * pg_dump to connect. Returns a status so the caller can decide whether
149
+ * to attempt a restore later. Stubbed in tests.
150
+ */
151
+ dumpEmbeddedDb: (currentDatabaseUrl: string) => Promise<EmbeddedDumpResult>;
152
+ /**
153
+ * Pipe a dumped snapshot into the canonical pgserve via `psql`. Called
154
+ * AFTER `pgserve install` has brought canonical online. No-op when the
155
+ * dump status was anything but `dumped`. Stubbed in tests.
156
+ */
157
+ restoreSnapshotToCanonical: (dump: EmbeddedDumpResult, canonicalDatabaseUrl: string) => Promise<{
158
+ status: 'restored' | 'skipped';
159
+ snapshotPath?: string;
160
+ }>;
161
+ /** Resolve canonical pgserve's on-disk data dir for operator-facing logs. */
162
+ getCanonicalPgserveDataDir: () => string;
144
163
  /**
145
164
  * Persist a partial server config (merges with existing). Stubbed in
146
165
  * tests so the canonical-pgserve fix can be validated without writing
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -21292,7 +21292,7 @@ var require_node_transport = __commonJS((exports) => {
21292
21292
  var util_1 = require_util();
21293
21293
  var tls_1 = __require("tls");
21294
21294
  var { resolve: resolve2 } = __require("path");
21295
- var { readFile, existsSync: existsSync9 } = __require("fs");
21295
+ var { readFile, existsSync: existsSync10 } = __require("fs");
21296
21296
  var dns = __require("dns");
21297
21297
  var VERSION2 = "2.29.3";
21298
21298
  var LANG = "nats.js";
@@ -21408,7 +21408,7 @@ var require_node_transport = __commonJS((exports) => {
21408
21408
  const d = (0, nats_base_client_1.deferred)();
21409
21409
  try {
21410
21410
  fn = resolve2(fn);
21411
- if (!existsSync9(fn)) {
21411
+ if (!existsSync10(fn)) {
21412
21412
  d.reject(new Error(`${fn} doesn't exist`));
21413
21413
  }
21414
21414
  readFile(fn, (err, data2) => {
@@ -26733,6 +26733,9 @@ function createSubscription(options) {
26733
26733
  return {
26734
26734
  id: subscriptionId,
26735
26735
  pattern,
26736
+ isAlive() {
26737
+ return isActive;
26738
+ },
26736
26739
  async unsubscribe() {
26737
26740
  isActive = false;
26738
26741
  abortController.abort();
@@ -27201,11 +27204,11 @@ var init_dead_letter = __esm(() => {
27201
27204
  });
27202
27205
 
27203
27206
  // ../core/src/events/payload-store.ts
27204
- import { gunzipSync, gzipSync } from "zlib";
27207
+ import { gunzipSync as gunzipSync2, gzipSync as gzipSync2 } from "zlib";
27205
27208
  function compressPayload(payload) {
27206
27209
  const jsonString = JSON.stringify(payload);
27207
27210
  const originalSize = Buffer.byteLength(jsonString, "utf8");
27208
- const compressed = gzipSync(jsonString);
27211
+ const compressed = gzipSync2(jsonString);
27209
27212
  const base64 = compressed.toString("base64");
27210
27213
  const compressedSize = compressed.length;
27211
27214
  return {
@@ -27217,7 +27220,7 @@ function compressPayload(payload) {
27217
27220
  }
27218
27221
  function decompressPayload(compressedBase64) {
27219
27222
  const buffer2 = Buffer.from(compressedBase64, "base64");
27220
- const decompressed = gunzipSync(buffer2);
27223
+ const decompressed = gunzipSync2(buffer2);
27221
27224
  const jsonString = decompressed.toString("utf8");
27222
27225
  return JSON.parse(jsonString);
27223
27226
  }
@@ -30755,7 +30758,7 @@ var require_pushgateway = __commonJS((exports, module) => {
30755
30758
  var url = __require("url");
30756
30759
  var http = __require("http");
30757
30760
  var https = __require("https");
30758
- var { gzipSync: gzipSync2 } = __require("zlib");
30761
+ var { gzipSync: gzipSync3 } = __require("zlib");
30759
30762
  var { globalRegistry } = require_registry();
30760
30763
 
30761
30764
  class Pushgateway {
@@ -30829,7 +30832,7 @@ var require_pushgateway = __commonJS((exports, module) => {
30829
30832
  if (method !== "DELETE") {
30830
30833
  this.registry.metrics().then((metrics) => {
30831
30834
  if (options.headers && options.headers["Content-Encoding"] === "gzip") {
30832
- metrics = gzipSync2(metrics);
30835
+ metrics = gzipSync3(metrics);
30833
30836
  }
30834
30837
  req.write(metrics);
30835
30838
  req.end();
@@ -34208,13 +34211,16 @@ var init_debounce = __esm(() => {
34208
34211
  // ../core/src/automations/engine.ts
34209
34212
  class AutomationEngine {
34210
34213
  config;
34211
- subscriptions = [];
34214
+ subscriptions = new Map;
34212
34215
  instanceQueues = new Map;
34213
34216
  debounceManagers = new Map;
34214
34217
  automations = [];
34215
34218
  eventBus = null;
34216
34219
  deps;
34217
34220
  logger = null;
34221
+ reconcileTimer = null;
34222
+ reconcileEnabled = false;
34223
+ reconcilePromise = null;
34218
34224
  constructor(config2) {
34219
34225
  this.config = config2;
34220
34226
  this.deps = {
@@ -34231,33 +34237,17 @@ class AutomationEngine {
34231
34237
  callAgent: deps.callAgent
34232
34238
  };
34233
34239
  this.automations = automations.filter((a) => a.enabled);
34234
- const triggerTypes = new Set(this.automations.map((a) => a.triggerEventType));
34235
- for (const eventType of triggerTypes) {
34236
- const durable = `automation-engine-${eventType.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
34237
- const subscription = await eventBus.subscribePattern(`${eventType}.>`, async (event) => {
34238
- await this.handleEvent(event);
34239
- }, {
34240
- durable,
34241
- queue: "automation-engine",
34242
- startFrom: "new",
34243
- maxRetries: 3,
34244
- retryDelayMs: 1000
34245
- });
34246
- this.subscriptions.push(subscription);
34247
- logger4.info(`Subscribed to ${eventType}.*`, { durable });
34248
- }
34249
- for (const automation of this.automations) {
34250
- if (automation.debounce && automation.debounce.mode !== "none") {
34251
- this.setupDebounceManager(automation);
34252
- }
34253
- }
34240
+ await this.reconcileSubscriptions();
34241
+ this.rebuildDebounceManagers();
34242
+ this.startReconcileTimer();
34254
34243
  logger4.info(`Automation engine started with ${this.automations.length} automations`);
34255
34244
  }
34256
34245
  async stop() {
34257
- for (const subscription of this.subscriptions) {
34246
+ this.stopReconcileTimer();
34247
+ for (const subscription of this.subscriptions.values()) {
34258
34248
  await subscription.unsubscribe();
34259
34249
  }
34260
- this.subscriptions = [];
34250
+ this.subscriptions.clear();
34261
34251
  for (const manager of this.debounceManagers.values()) {
34262
34252
  manager.flushAll();
34263
34253
  }
@@ -34268,9 +34258,117 @@ class AutomationEngine {
34268
34258
  this.logger = logger5;
34269
34259
  }
34270
34260
  async reload(automations) {
34271
- await this.stop();
34272
- if (this.eventBus) {
34273
- await this.start(this.eventBus, automations, this.deps);
34261
+ if (!this.eventBus) {
34262
+ this.automations = automations.filter((a) => a.enabled);
34263
+ return;
34264
+ }
34265
+ this.automations = automations.filter((a) => a.enabled);
34266
+ await this.reconcileSubscriptions();
34267
+ this.rebuildDebounceManagers();
34268
+ }
34269
+ async reconcile() {
34270
+ if (!this.eventBus)
34271
+ return;
34272
+ await this.reconcileSubscriptions();
34273
+ }
34274
+ async reconcileSubscriptions() {
34275
+ if (!this.eventBus)
34276
+ return;
34277
+ if (this.reconcilePromise)
34278
+ return this.reconcilePromise;
34279
+ this.reconcilePromise = this.doReconcileSubscriptions().finally(() => {
34280
+ this.reconcilePromise = null;
34281
+ });
34282
+ return this.reconcilePromise;
34283
+ }
34284
+ async doReconcileSubscriptions() {
34285
+ if (!this.eventBus)
34286
+ return;
34287
+ const expectedTriggers = new Set(this.automations.map((a) => a.triggerEventType));
34288
+ for (const [eventType, subscription] of this.subscriptions) {
34289
+ if (!expectedTriggers.has(eventType)) {
34290
+ await subscription.unsubscribe().catch((err2) => {
34291
+ logger4.warn("Failed to unsubscribe orphan trigger", { eventType, error: String(err2) });
34292
+ });
34293
+ this.subscriptions.delete(eventType);
34294
+ logger4.info(`Unsubscribed from ${eventType}.* (no enabled automations)`);
34295
+ }
34296
+ }
34297
+ for (const eventType of expectedTriggers) {
34298
+ const existing = this.subscriptions.get(eventType);
34299
+ if (existing && this.isSubscriptionAlive(existing)) {
34300
+ continue;
34301
+ }
34302
+ if (existing) {
34303
+ await existing.unsubscribe().catch(() => {});
34304
+ this.subscriptions.delete(eventType);
34305
+ logger4.warn("Replacing dead subscription", { eventType });
34306
+ }
34307
+ const subscription = await this.subscribeForTrigger(eventType);
34308
+ this.subscriptions.set(eventType, subscription);
34309
+ }
34310
+ }
34311
+ async subscribeForTrigger(eventType) {
34312
+ if (!this.eventBus) {
34313
+ throw new Error("Event bus not initialized");
34314
+ }
34315
+ const durable = `automation-engine-${eventType.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
34316
+ const subscription = await this.eventBus.subscribePattern(`${eventType}.>`, async (event) => {
34317
+ await this.handleEvent(event);
34318
+ }, {
34319
+ durable,
34320
+ queue: "automation-engine",
34321
+ startFrom: "new",
34322
+ maxRetries: 3,
34323
+ retryDelayMs: 1000
34324
+ });
34325
+ logger4.info(`Subscribed to ${eventType}.*`, { durable });
34326
+ return subscription;
34327
+ }
34328
+ isSubscriptionAlive(subscription) {
34329
+ return subscription.isAlive ? subscription.isAlive() : true;
34330
+ }
34331
+ rebuildDebounceManagers() {
34332
+ const enabledIds = new Set(this.automations.map((a) => a.id));
34333
+ for (const id of this.debounceManagers.keys()) {
34334
+ if (!enabledIds.has(id)) {
34335
+ this.debounceManagers.get(id)?.flushAll();
34336
+ this.debounceManagers.delete(id);
34337
+ }
34338
+ }
34339
+ for (const automation of this.automations) {
34340
+ if (automation.debounce && automation.debounce.mode !== "none" && !this.debounceManagers.has(automation.id)) {
34341
+ this.setupDebounceManager(automation);
34342
+ }
34343
+ }
34344
+ }
34345
+ startReconcileTimer() {
34346
+ this.stopReconcileTimer();
34347
+ const intervalMs = this.config.reconcileIntervalMs ?? 30000;
34348
+ if (intervalMs <= 0)
34349
+ return;
34350
+ this.reconcileEnabled = true;
34351
+ this.scheduleNextReconcile(intervalMs);
34352
+ }
34353
+ scheduleNextReconcile(intervalMs) {
34354
+ if (!this.reconcileEnabled)
34355
+ return;
34356
+ this.reconcileTimer = setTimeout(() => {
34357
+ this.reconcileSubscriptions().catch((err2) => {
34358
+ logger4.error("Reconciler tick failed", { error: String(err2) });
34359
+ }).finally(() => {
34360
+ this.scheduleNextReconcile(intervalMs);
34361
+ });
34362
+ }, intervalMs);
34363
+ if (typeof this.reconcileTimer === "object" && this.reconcileTimer && "unref" in this.reconcileTimer) {
34364
+ this.reconcileTimer.unref();
34365
+ }
34366
+ }
34367
+ stopReconcileTimer() {
34368
+ this.reconcileEnabled = false;
34369
+ if (this.reconcileTimer) {
34370
+ clearTimeout(this.reconcileTimer);
34371
+ this.reconcileTimer = null;
34274
34372
  }
34275
34373
  }
34276
34374
  async handleEvent(event) {
@@ -52103,8 +52201,8 @@ var init_a2a_provider = __esm(() => {
52103
52201
 
52104
52202
  // ../core/src/providers/nats-genie-provider.ts
52105
52203
  import { mkdir, writeFile } from "fs/promises";
52106
- import { homedir as homedir7 } from "os";
52107
- import { join as join12 } from "path";
52204
+ import { homedir as homedir8 } from "os";
52205
+ import { join as join13 } from "path";
52108
52206
 
52109
52207
  class NatsGenieProvider {
52110
52208
  id;
@@ -52294,10 +52392,10 @@ class NatsGenieProvider {
52294
52392
  }
52295
52393
  async writeDeadLetter(payload, error2) {
52296
52394
  try {
52297
- const dlDir = join12(homedir7(), ".omni", "dead-letters");
52395
+ const dlDir = join13(homedir8(), ".omni", "dead-letters");
52298
52396
  await mkdir(dlDir, { recursive: true });
52299
52397
  const filename = `nats-genie-${Date.now()}-${payload.chatId}.json`;
52300
- await writeFile(join12(dlDir, filename), JSON.stringify({
52398
+ await writeFile(join13(dlDir, filename), JSON.stringify({
52301
52399
  payload,
52302
52400
  error: error2 instanceof Error ? error2.message : String(error2),
52303
52401
  timestamp: new Date().toISOString()
@@ -54160,7 +54258,7 @@ var init_sql = __esm(() => {
54160
54258
  return new SQL([new StringChunk(str)]);
54161
54259
  }
54162
54260
  sql2.raw = raw2;
54163
- function join13(chunks, separator) {
54261
+ function join14(chunks, separator) {
54164
54262
  const result = [];
54165
54263
  for (const [i2, chunk] of chunks.entries()) {
54166
54264
  if (i2 > 0 && separator !== undefined) {
@@ -54170,7 +54268,7 @@ var init_sql = __esm(() => {
54170
54268
  }
54171
54269
  return new SQL(result);
54172
54270
  }
54173
- sql2.join = join13;
54271
+ sql2.join = join14;
54174
54272
  function identifier(value) {
54175
54273
  return new Name(value);
54176
54274
  }
@@ -57397,7 +57495,7 @@ var init_select2 = __esm(() => {
57397
57495
  return (table2, on) => {
57398
57496
  const baseTableName = this.tableName;
57399
57497
  const tableName = getTableLikeName(table2);
57400
- if (typeof tableName === "string" && this.config.joins?.some((join13) => join13.alias === tableName)) {
57498
+ if (typeof tableName === "string" && this.config.joins?.some((join14) => join14.alias === tableName)) {
57401
57499
  throw new Error(`Alias "${tableName}" is already used in this query`);
57402
57500
  }
57403
57501
  if (!this.isPartialSelect) {
@@ -57908,7 +58006,7 @@ var init_update = __esm(() => {
57908
58006
  createJoin(joinType) {
57909
58007
  return (table2, on) => {
57910
58008
  const tableName = getTableLikeName(table2);
57911
- if (typeof tableName === "string" && this.config.joins.some((join13) => join13.alias === tableName)) {
58009
+ if (typeof tableName === "string" && this.config.joins.some((join14) => join14.alias === tableName)) {
57912
58010
  throw new Error(`Alias "${tableName}" is already used in this query`);
57913
58011
  }
57914
58012
  if (typeof on === "function") {
@@ -57958,10 +58056,10 @@ var init_update = __esm(() => {
57958
58056
  const fromFields = this.getTableLikeFields(this.config.from);
57959
58057
  fields[tableName] = fromFields;
57960
58058
  }
57961
- for (const join13 of this.config.joins) {
57962
- const tableName2 = getTableLikeName(join13.table);
57963
- if (typeof tableName2 === "string" && !is(join13.table, SQL)) {
57964
- const fromFields = this.getTableLikeFields(join13.table);
58059
+ for (const join14 of this.config.joins) {
58060
+ const tableName2 = getTableLikeName(join14.table);
58061
+ if (typeof tableName2 === "string" && !is(join14.table, SQL)) {
58062
+ const fromFields = this.getTableLikeFields(join14.table);
57965
58063
  fields[tableName2] = fromFields;
57966
58064
  }
57967
58065
  }
@@ -78097,7 +78195,7 @@ var require_path = __commonJS((exports) => {
78097
78195
  function isAbsolute(path) {
78098
78196
  return path.charAt(0) === "/";
78099
78197
  }
78100
- function join15(...args) {
78198
+ function join16(...args) {
78101
78199
  return normalizePath(args.join("/"));
78102
78200
  }
78103
78201
  function dirname6(path) {
@@ -78122,7 +78220,7 @@ var require_path = __commonJS((exports) => {
78122
78220
  exports.basename = basename6;
78123
78221
  exports.dirname = dirname6;
78124
78222
  exports.isAbsolute = isAbsolute;
78125
- exports.join = join15;
78223
+ exports.join = join16;
78126
78224
  exports.normalizePath = normalizePath;
78127
78225
  exports.relative = relative;
78128
78226
  exports.resolve = resolve4;
@@ -114079,7 +114177,7 @@ import { fileURLToPath } from "url";
114079
114177
  // package.json
114080
114178
  var package_default = {
114081
114179
  name: "@automagik/omni",
114082
- version: "2.260430.16",
114180
+ version: "2.260501.2",
114083
114181
  description: "LLM-optimized CLI for Omni",
114084
114182
  type: "module",
114085
114183
  bin: {
@@ -117619,9 +117717,9 @@ function createDeadLettersCommand() {
117619
117717
  }
117620
117718
 
117621
117719
  // src/commands/doctor.ts
117622
- import { existsSync as existsSync6, readdirSync as readdirSync2, statSync } from "fs";
117623
- import { homedir as homedir5 } from "os";
117624
- import { join as join9, resolve } from "path";
117720
+ import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
117721
+ import { homedir as homedir6 } from "os";
117722
+ import { join as join10, resolve } from "path";
117625
117723
  init_config();
117626
117724
 
117627
117725
  // src/health.ts
@@ -118732,6 +118830,11 @@ WantedBy=multi-user.target
118732
118830
  // src/lib/canonical-pgserve.ts
118733
118831
  init_config();
118734
118832
  init_output();
118833
+ import { spawnSync } from "child_process";
118834
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync, statSync, writeFileSync as writeFileSync5 } from "fs";
118835
+ import { homedir as homedir5 } from "os";
118836
+ import { join as join8 } from "path";
118837
+ import { gunzipSync, gzipSync } from "zlib";
118735
118838
  var PGSERVE_REQUIRED_VERSION = "^2.1.0";
118736
118839
  async function isPgserveInstalled() {
118737
118840
  try {
@@ -118825,30 +118928,148 @@ async function resolveCanonicalPgservePreference(isReinstall, cfg) {
118825
118928
  raw("");
118826
118929
  return true;
118827
118930
  }
118931
+ var OMNI_EMBEDDED_PGSERVE_DATA_DIR = join8(homedir5(), ".omni", "data", "pgserve");
118932
+ var PGSERVE_DEFAULT_DATA_DIR = join8(homedir5(), ".pgserve", "data");
118933
+ var PGSERVE_CONFIG_PATH = join8(homedir5(), ".pgserve", "config.json");
118934
+ var OMNI_BACKUPS_DIR = join8(homedir5(), ".omni", "backups");
118935
+ function getEmbeddedPgserveDataDir() {
118936
+ return OMNI_EMBEDDED_PGSERVE_DATA_DIR;
118937
+ }
118938
+ function getCanonicalPgserveDataDir() {
118939
+ if (!existsSync6(PGSERVE_CONFIG_PATH))
118940
+ return PGSERVE_DEFAULT_DATA_DIR;
118941
+ try {
118942
+ const raw2 = readFileSync4(PGSERVE_CONFIG_PATH, "utf8");
118943
+ const parsed = JSON.parse(raw2);
118944
+ return typeof parsed.dataDir === "string" && parsed.dataDir.length > 0 ? parsed.dataDir : PGSERVE_DEFAULT_DATA_DIR;
118945
+ } catch {
118946
+ return PGSERVE_DEFAULT_DATA_DIR;
118947
+ }
118948
+ }
118949
+ function looksLikePgDataDir(path) {
118950
+ return existsSync6(join8(path, "PG_VERSION")) && existsSync6(join8(path, "base"));
118951
+ }
118952
+ function getSnapshotPath(timestamp = new Date) {
118953
+ const ts = timestamp.toISOString().replace(/[:.]/g, "-");
118954
+ return join8(OMNI_BACKUPS_DIR, `embedded-migration-${ts}.sql.gz`);
118955
+ }
118956
+ function commandIsAvailable(cmd) {
118957
+ try {
118958
+ const result = spawnSync(cmd, ["--version"], { stdio: ["ignore", "pipe", "pipe"], timeout: 3000 });
118959
+ return result.status === 0;
118960
+ } catch {
118961
+ return false;
118962
+ }
118963
+ }
118964
+ function pgEnvFromUrl(url) {
118965
+ const parsed = new URL(url);
118966
+ const env2 = {
118967
+ PGHOST: parsed.hostname,
118968
+ PGUSER: decodeURIComponent(parsed.username || "postgres"),
118969
+ PGDATABASE: parsed.pathname.replace(/^\//, "") || "omni"
118970
+ };
118971
+ if (parsed.port)
118972
+ env2.PGPORT = parsed.port;
118973
+ if (parsed.password)
118974
+ env2.PGPASSWORD = decodeURIComponent(parsed.password);
118975
+ return env2;
118976
+ }
118977
+ async function dumpEmbeddedDb(currentDatabaseUrl) {
118978
+ const embeddedDir = getEmbeddedPgserveDataDir();
118979
+ if (!existsSync6(embeddedDir)) {
118980
+ return { status: "no-embedded-data", embeddedDir };
118981
+ }
118982
+ if (!looksLikePgDataDir(embeddedDir)) {
118983
+ warn(`Embedded pgserve dir at ${embeddedDir} is missing PG_VERSION or base/ \u2014 does not look like a Postgres data dir. Skipping dump; canonical will start empty.`);
118984
+ return { status: "embedded-data-invalid", embeddedDir };
118985
+ }
118986
+ if (!commandIsAvailable("pg_dump")) {
118987
+ throw new Error("pg_dump not found in PATH \u2014 install postgresql-client (apt install postgresql-client / brew install postgresql) and retry");
118988
+ }
118989
+ const snapshotPath = getSnapshotPath();
118990
+ mkdirSync4(OMNI_BACKUPS_DIR, { recursive: true, mode: 448 });
118991
+ raw(" Dumping embedded database via pg_dump...");
118992
+ raw(` source data dir: ${embeddedDir}`);
118993
+ raw(` snapshot: ${snapshotPath}`);
118994
+ const result = spawnSync("pg_dump", ["--no-owner", "--no-acl", "--clean", "--if-exists"], {
118995
+ env: { ...process.env, ...pgEnvFromUrl(currentDatabaseUrl) },
118996
+ stdio: ["pipe", "pipe", "pipe"],
118997
+ timeout: 600000,
118998
+ maxBuffer: 1024 * 1024 * 1024 * 4
118999
+ });
119000
+ if (result.status !== 0) {
119001
+ const stderr = result.stderr?.toString().trim() || "unknown error";
119002
+ throw new Error(`pg_dump failed (exit ${result.status}): ${stderr}`);
119003
+ }
119004
+ const compressed = gzipSync(result.stdout);
119005
+ const tmpPath = `${snapshotPath}.tmp`;
119006
+ writeFileSync5(tmpPath, compressed, { mode: 384 });
119007
+ renameSync(tmpPath, snapshotPath);
119008
+ const bytes = statSync(snapshotPath).size;
119009
+ raw(` snapshot size: ${formatBytes(bytes)}`);
119010
+ await Promise.resolve();
119011
+ return { status: "dumped", embeddedDir, snapshotPath, bytes };
119012
+ }
119013
+ async function restoreSnapshotToCanonical(dump, canonicalDatabaseUrl) {
119014
+ if (dump.status !== "dumped") {
119015
+ return { status: "skipped" };
119016
+ }
119017
+ if (!commandIsAvailable("psql")) {
119018
+ throw new Error("psql not found in PATH \u2014 install postgresql-client (apt install postgresql-client / brew install postgresql) and retry");
119019
+ }
119020
+ raw(" Restoring snapshot into canonical pgserve via psql...");
119021
+ raw(` snapshot: ${dump.snapshotPath}`);
119022
+ raw(` canonical data dir: ${getCanonicalPgserveDataDir()}`);
119023
+ raw(` canonical URL: ${canonicalDatabaseUrl}`);
119024
+ const compressed = readFileSync4(dump.snapshotPath);
119025
+ const sql = gunzipSync(compressed);
119026
+ const result = spawnSync("psql", ["-v", "ON_ERROR_STOP=1"], {
119027
+ env: { ...process.env, ...pgEnvFromUrl(canonicalDatabaseUrl) },
119028
+ input: sql,
119029
+ stdio: ["pipe", "pipe", "pipe"],
119030
+ timeout: 600000,
119031
+ maxBuffer: 1024 * 1024 * 1024 * 4
119032
+ });
119033
+ if (result.status !== 0) {
119034
+ const stderr = result.stderr?.toString().trim() || "unknown error";
119035
+ throw new Error(`psql restore failed (exit ${result.status}): ${stderr}`);
119036
+ }
119037
+ await Promise.resolve();
119038
+ return { status: "restored", snapshotPath: dump.snapshotPath };
119039
+ }
119040
+ function formatBytes(n) {
119041
+ if (n < 1024)
119042
+ return `${n} B`;
119043
+ if (n < 1024 * 1024)
119044
+ return `${(n / 1024).toFixed(1)} KB`;
119045
+ if (n < 1024 * 1024 * 1024)
119046
+ return `${(n / 1024 / 1024).toFixed(1)} MB`;
119047
+ return `${(n / 1024 / 1024 / 1024).toFixed(2)} GB`;
119048
+ }
118828
119049
 
118829
119050
  // src/commands/doctor.ts
118830
119051
  init_output();
118831
119052
 
118832
119053
  // src/server-bundle.ts
118833
119054
  init_output();
118834
- import { dirname as dirname2, join as join8 } from "path";
119055
+ import { dirname as dirname2, join as join9 } from "path";
118835
119056
  import { fileURLToPath as fileURLToPath2 } from "url";
118836
119057
  function getServerBundlePath() {
118837
119058
  try {
118838
119059
  const thisFile = fileURLToPath2(import.meta.url);
118839
119060
  const distDir = dirname2(thisFile);
118840
- return join8(distDir, "server", "index.js");
119061
+ return join9(distDir, "server", "index.js");
118841
119062
  } catch {
118842
- return join8(process.cwd(), "dist", "server", "index.js");
119063
+ return join9(process.cwd(), "dist", "server", "index.js");
118843
119064
  }
118844
119065
  }
118845
119066
  function getServerLauncherPath() {
118846
119067
  try {
118847
119068
  const thisFile = fileURLToPath2(import.meta.url);
118848
119069
  const distDir = dirname2(thisFile);
118849
- return join8(distDir, "..", "bin", "omni-server");
119070
+ return join9(distDir, "..", "bin", "omni-server");
118850
119071
  } catch {
118851
- return join8(process.cwd(), "bin", "omni-server");
119072
+ return join9(process.cwd(), "bin", "omni-server");
118852
119073
  }
118853
119074
  }
118854
119075
  function bundleNotFoundError(bundlePath) {
@@ -118913,10 +119134,10 @@ function productionDeps() {
118913
119134
  }
118914
119135
  },
118915
119136
  findOrphanedDataDirs: () => {
118916
- const roots = [process.cwd(), join9(homedir5(), "workspace"), join9(homedir5(), "repos")];
119137
+ const roots = [process.cwd(), join10(homedir6(), "workspace"), join10(homedir6(), "repos")];
118917
119138
  const found = [];
118918
119139
  for (const root of roots) {
118919
- if (!existsSync6(root))
119140
+ if (!existsSync7(root))
118920
119141
  continue;
118921
119142
  try {
118922
119143
  scanForOrphans(root, found, 0);
@@ -118982,6 +119203,9 @@ function productionDeps() {
118982
119203
  },
118983
119204
  cliHasSigningKey: () => loadSigningContext() !== null,
118984
119205
  setupCanonicalPgserve,
119206
+ dumpEmbeddedDb,
119207
+ restoreSnapshotToCanonical,
119208
+ getCanonicalPgserveDataDir,
118985
119209
  saveServerConfig
118986
119210
  };
118987
119211
  }
@@ -118997,10 +119221,10 @@ function scanForOrphans(dir, acc, depth, maxDepth = 4) {
118997
119221
  for (const name of entries) {
118998
119222
  if (name === "node_modules" || name === ".git")
118999
119223
  continue;
119000
- const full = join9(dir, name);
119224
+ const full = join10(dir, name);
119001
119225
  let stats;
119002
119226
  try {
119003
- stats = statSync(full);
119227
+ stats = statSync2(full);
119004
119228
  } catch {
119005
119229
  continue;
119006
119230
  }
@@ -119253,12 +119477,26 @@ async function fixCliKeyValid(deps) {
119253
119477
  }
119254
119478
  async function fixPgserveCanonical(deps) {
119255
119479
  const { serverConfig, cliConfig } = deps.loadState();
119480
+ let dumpResult;
119481
+ try {
119482
+ dumpResult = await deps.dumpEmbeddedDb(serverConfig.databaseUrl);
119483
+ } catch (err) {
119484
+ throw new Error(`pg_dump of embedded omni DB failed (${err instanceof Error ? err.message : String(err)}); omni-api still running on embedded \u2014 install postgresql-client (apt install postgresql-client) if pg_dump is missing, then retry`);
119485
+ }
119256
119486
  await deps.runPm2(["stop", PM2_PROCESSES.api]);
119257
119487
  const url = await deps.setupCanonicalPgserve();
119258
119488
  if (!url) {
119259
119489
  await deps.runPm2(["start", PM2_PROCESSES.api]);
119260
119490
  throw new Error("canonical pgserve setup failed (pgserve binary unavailable or install failed) \u2014 install manually: bun add -g pgserve@^2.1.0");
119261
119491
  }
119492
+ let restoreOutcome;
119493
+ try {
119494
+ restoreOutcome = await deps.restoreSnapshotToCanonical(dumpResult, url);
119495
+ } catch (err) {
119496
+ await deps.runPm2(["start", PM2_PROCESSES.api]);
119497
+ const snapshotHint = dumpResult.status === "dumped" ? ` snapshot preserved at ${dumpResult.snapshotPath}` : "";
119498
+ throw new Error(`psql restore into canonical pgserve failed (${err instanceof Error ? err.message : String(err)}); omni-api restarted on embedded \u2014${snapshotHint} retry by replaying the dump manually or re-running \`omni doctor --fix\``);
119499
+ }
119262
119500
  deps.saveServerConfig({ databaseUrl: url, useCanonicalPgserve: true });
119263
119501
  const env2 = buildRuntimeEnv({ ...serverConfig, databaseUrl: url, useCanonicalPgserve: true }, cliConfig);
119264
119502
  await deps.runPm2(["delete", PM2_PROCESSES.api], env2);
@@ -119272,7 +119510,9 @@ async function fixPgserveCanonical(deps) {
119272
119510
  if (startCode !== 0) {
119273
119511
  throw new Error(`pm2 start ${PM2_PROCESSES.api} exited ${startCode} after canonical migration`);
119274
119512
  }
119275
- return `migrated to canonical pgserve@^2.1.0; omni-api now connects to ${url}`;
119513
+ const canonicalDir = deps.getCanonicalPgserveDataDir();
119514
+ const dataNote = dumpResult.status === "dumped" && restoreOutcome.status === "restored" ? `restored ${dumpResult.snapshotPath} into ${canonicalDir} (omni-api \u2192 ${url})` : dumpResult.status === "no-embedded-data" ? `no embedded data to migrate; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})` : `embedded data dir invalid; canonical started empty at ${canonicalDir} (omni-api \u2192 ${url})`;
119515
+ return `migrated to canonical pgserve@^2.1.0; ${dataNote}`;
119276
119516
  }
119277
119517
  function fixOrphanedDataDirs(deps) {
119278
119518
  const found = deps.findOrphanedDataDirs();
@@ -119472,7 +119712,7 @@ Safety:
119472
119712
  }
119473
119713
 
119474
119714
  // src/commands/done.ts
119475
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
119715
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "fs";
119476
119716
  import { basename, extname } from "path";
119477
119717
 
119478
119718
  // src/context.ts
@@ -119581,12 +119821,12 @@ async function handleReact(client, ctx, emoji) {
119581
119821
  await closeTurn(client, "react", `Reacted ${emoji} + turn closed`);
119582
119822
  }
119583
119823
  async function handleMedia(client, ctx, mediaPath, caption) {
119584
- if (!existsSync7(mediaPath)) {
119824
+ if (!existsSync8(mediaPath)) {
119585
119825
  return error(`File not found: ${mediaPath}`);
119586
119826
  }
119587
119827
  try {
119588
119828
  const mediaType = getMediaType(mediaPath);
119589
- const buffer = readFileSync4(mediaPath);
119829
+ const buffer = readFileSync5(mediaPath);
119590
119830
  const base64 = buffer.toString("base64");
119591
119831
  const filename = basename(mediaPath);
119592
119832
  await client.messages.sendMedia({
@@ -120018,7 +120258,7 @@ function createEventsCommand() {
120018
120258
  }
120019
120259
 
120020
120260
  // src/commands/film.ts
120021
- import { writeFileSync as writeFileSync5 } from "fs";
120261
+ import { writeFileSync as writeFileSync6 } from "fs";
120022
120262
  import { resolve as resolvePath } from "path";
120023
120263
  init_output();
120024
120264
  function createFilmCommand() {
@@ -120082,7 +120322,7 @@ function createFilmCommand() {
120082
120322
  const videoBuffer = Buffer.from(result.videoBase64, "base64");
120083
120323
  if (options.output) {
120084
120324
  const outPath = resolvePath(options.output);
120085
- writeFileSync5(outPath, videoBuffer);
120325
+ writeFileSync6(outPath, videoBuffer);
120086
120326
  success("Video saved", { path: outPath, sizeBytes: videoBuffer.length });
120087
120327
  return;
120088
120328
  }
@@ -120106,7 +120346,7 @@ function createFilmCommand() {
120106
120346
  }
120107
120347
 
120108
120348
  // src/commands/follow-up.ts
120109
- import { readFileSync as readFileSync5 } from "fs";
120349
+ import { readFileSync as readFileSync6 } from "fs";
120110
120350
  init_output();
120111
120351
  var VALID_SCOPES = ["agents", "instances", "chats"];
120112
120352
  function assertScope(scope) {
@@ -120124,9 +120364,9 @@ async function resolveScopedId(scope, id) {
120124
120364
  function readJsonArg(raw2) {
120125
120365
  let body = raw2;
120126
120366
  if (body === "-") {
120127
- body = readFileSync5(0, "utf8");
120367
+ body = readFileSync6(0, "utf8");
120128
120368
  } else if (body.startsWith("@")) {
120129
- body = readFileSync5(body.slice(1), "utf8");
120369
+ body = readFileSync6(body.slice(1), "utf8");
120130
120370
  }
120131
120371
  try {
120132
120372
  return JSON.parse(body);
@@ -120278,8 +120518,8 @@ function createHistoryCommand() {
120278
120518
  }
120279
120519
 
120280
120520
  // src/commands/imagine.ts
120281
- import { writeFileSync as writeFileSync6 } from "fs";
120282
- import { basename as basename2, dirname as dirname3, extname as extname2, join as join10 } from "path";
120521
+ import { writeFileSync as writeFileSync7 } from "fs";
120522
+ import { basename as basename2, dirname as dirname3, extname as extname2, join as join11 } from "path";
120283
120523
  init_output();
120284
120524
  var ALLOWED_ASPECT_RATIOS = ["1:1", "4:3", "3:4", "16:9", "9:16", "3:2", "2:3"];
120285
120525
  function extensionForMime(mimeType) {
@@ -120294,9 +120534,9 @@ function buildOutputPath(outputBase, index, total, mimeType) {
120294
120534
  const ext = extname2(outputBase) || extensionForMime(mimeType);
120295
120535
  const stem = basename2(outputBase, extname2(outputBase));
120296
120536
  if (total <= 1) {
120297
- return join10(dir, `${stem}${ext}`);
120537
+ return join11(dir, `${stem}${ext}`);
120298
120538
  }
120299
- return join10(dir, `${stem}-${index + 1}${ext}`);
120539
+ return join11(dir, `${stem}-${index + 1}${ext}`);
120300
120540
  }
120301
120541
  function parseAspectRatio(value) {
120302
120542
  if (!value)
@@ -120369,7 +120609,7 @@ function createImagineCommand() {
120369
120609
  continue;
120370
120610
  const path = buildOutputPath(options.output, i, result.images.length, image.mimeType);
120371
120611
  try {
120372
- writeFileSync6(path, Buffer.from(image.base64, "base64"));
120612
+ writeFileSync7(path, Buffer.from(image.base64, "base64"));
120373
120613
  savedPaths.push(path);
120374
120614
  } catch (err) {
120375
120615
  const message = err instanceof Error ? err.message : "Unknown error";
@@ -120414,12 +120654,12 @@ function createImagineCommand() {
120414
120654
  }
120415
120655
 
120416
120656
  // src/commands/install.ts
120417
- import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
120418
- import { homedir as homedir6 } from "os";
120419
- import { join as join11 } from "path";
120657
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5 } from "fs";
120658
+ import { homedir as homedir7 } from "os";
120659
+ import { join as join12 } from "path";
120420
120660
  init_config();
120421
120661
  init_output();
120422
- var DEFAULT_DATA_DIR2 = join11(homedir6(), ".omni", "data");
120662
+ var DEFAULT_DATA_DIR2 = join12(homedir7(), ".omni", "data");
120423
120663
  function computeDefaultDatabaseUrl() {
120424
120664
  return buildEmbeddedDatabaseUrl();
120425
120665
  }
@@ -120518,13 +120758,13 @@ async function startServices(cfg, forceCleanup, forceSystemd, useCanonicalPgserv
120518
120758
  return false;
120519
120759
  }
120520
120760
  const bundlePath = getServerBundlePath();
120521
- if (!existsSync8(bundlePath)) {
120761
+ if (!existsSync9(bundlePath)) {
120522
120762
  warn(`Server bundle not found at: ${bundlePath}
120523
120763
  Install @automagik/omni from npm: bun add -g @automagik/omni
120524
120764
  Or build locally: make cli-build-full`);
120525
120765
  return false;
120526
120766
  }
120527
- mkdirSync4(getPm2LogDir(), { recursive: true });
120767
+ mkdirSync5(getPm2LogDir(), { recursive: true });
120528
120768
  await installPm2Logrotate();
120529
120769
  const runtimeEnv = buildInstallRuntimeEnv(cfg, forceCleanup, useCanonicalPgserve);
120530
120770
  await runPm2(["delete", PM2_PROCESSES.api]);
@@ -120542,10 +120782,10 @@ async function startServices(cfg, forceCleanup, forceSystemd, useCanonicalPgserv
120542
120782
  return false;
120543
120783
  }
120544
120784
  apiSpinner.succeed(`${PM2_PROCESSES.api} started`);
120545
- if (existsSync8(NATS_BINARY_PATH)) {
120785
+ if (existsSync9(NATS_BINARY_PATH)) {
120546
120786
  const natsSpinner = ora(`Starting ${PM2_PROCESSES.nats}...`).start();
120547
- const natsDataDir = join11(cfg.dataDir, "nats");
120548
- mkdirSync4(natsDataDir, { recursive: true });
120787
+ const natsDataDir = join12(cfg.dataDir, "nats");
120788
+ mkdirSync5(natsDataDir, { recursive: true });
120549
120789
  const natsArgs = buildPm2StartArgs({
120550
120790
  kind: "nats",
120551
120791
  script: NATS_BINARY_PATH,
@@ -122221,7 +122461,7 @@ function createLogsCommand() {
122221
122461
  }
122222
122462
 
122223
122463
  // src/commands/media.ts
122224
- import { createWriteStream as createWriteStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, statSync as statSync3 } from "fs";
122464
+ import { createWriteStream as createWriteStream2, existsSync as existsSync11, mkdirSync as mkdirSync7, statSync as statSync4 } from "fs";
122225
122465
  import { basename as basename4, dirname as dirname4, resolve as resolve2 } from "path";
122226
122466
  import { Readable } from "stream";
122227
122467
  import { pipeline } from "stream/promises";
@@ -122284,14 +122524,14 @@ function resolveOutputPath(outputPath, result) {
122284
122524
  const resolved = resolve2(outputPath);
122285
122525
  if (isDirHint)
122286
122526
  return resolve2(resolved, filename);
122287
- if (existsSync10(resolved) && statSync3(resolved).isDirectory())
122527
+ if (existsSync11(resolved) && statSync4(resolved).isDirectory())
122288
122528
  return resolve2(resolved, filename);
122289
122529
  return resolved;
122290
122530
  }
122291
122531
  async function downloadToFile(url, apiKey, destinationPath) {
122292
122532
  const destDir = dirname4(destinationPath);
122293
- if (!existsSync10(destDir))
122294
- mkdirSync6(destDir, { recursive: true });
122533
+ if (!existsSync11(destDir))
122534
+ mkdirSync7(destDir, { recursive: true });
122295
122535
  const resp = await fetch(url, {
122296
122536
  method: "GET",
122297
122537
  headers: apiKey ? { "x-api-key": apiKey } : undefined
@@ -123244,8 +123484,8 @@ init_output();
123244
123484
  init_src();
123245
123485
  import { execFileSync as execFileSync2, execSync } from "child_process";
123246
123486
  import * as nodeCrypto2 from "crypto";
123247
- import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
123248
- import { homedir as homedir8 } from "os";
123487
+ import { existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "fs";
123488
+ import { homedir as homedir9 } from "os";
123249
123489
  import { dirname as dirname5, resolve as resolve3 } from "path";
123250
123490
  import { createInterface as createInterface2 } from "readline";
123251
123491
  init_config();
@@ -123472,19 +123712,19 @@ async function pairDevice(gatewayUrl, gatewayToken, keypair, spinner) {
123472
123712
  ws.close(1000, "pairing complete");
123473
123713
  }
123474
123714
  }
123475
- var OPENCLAW_CONFIG_PATH = resolve3(homedir8(), ".openclaw", "openclaw.json");
123715
+ var OPENCLAW_CONFIG_PATH = resolve3(homedir9(), ".openclaw", "openclaw.json");
123476
123716
  var PLUGIN_MARKER = "plugin-openclaw/omni.ts";
123477
123717
  function readOpenClawConfig(configPath) {
123478
- if (!existsSync11(configPath))
123718
+ if (!existsSync12(configPath))
123479
123719
  return {};
123480
- const raw2 = readFileSync7(configPath, "utf-8").trim();
123720
+ const raw2 = readFileSync8(configPath, "utf-8").trim();
123481
123721
  if (!raw2)
123482
123722
  return {};
123483
123723
  return JSON.parse(raw2);
123484
123724
  }
123485
123725
  function writeOpenClawConfig(configPath, config2) {
123486
- mkdirSync7(dirname5(configPath), { recursive: true, mode: 448 });
123487
- writeFileSync7(configPath, `${JSON.stringify(config2, null, 2)}
123726
+ mkdirSync8(dirname5(configPath), { recursive: true, mode: 448 });
123727
+ writeFileSync8(configPath, `${JSON.stringify(config2, null, 2)}
123488
123728
  `, { mode: 384 });
123489
123729
  }
123490
123730
  function isPluginRegistered(config2, marker, pluginPath) {
@@ -123516,10 +123756,10 @@ function isValidUuid2(value) {
123516
123756
  }
123517
123757
  function resolvePluginPath(explicit) {
123518
123758
  if (explicit) {
123519
- return existsSync11(explicit) ? resolve3(explicit) : null;
123759
+ return existsSync12(explicit) ? resolve3(explicit) : null;
123520
123760
  }
123521
123761
  const cwdCandidate = resolve3(process.cwd(), "packages/plugin-openclaw/omni.ts");
123522
- return existsSync11(cwdCandidate) ? cwdCandidate : null;
123762
+ return existsSync12(cwdCandidate) ? cwdCandidate : null;
123523
123763
  }
123524
123764
  function registerPlugin(config2, pluginPath, configPath) {
123525
123765
  if (hasOpenClawCli()) {
@@ -124408,7 +124648,7 @@ function createSayCommand() {
124408
124648
  }
124409
124649
 
124410
124650
  // src/commands/see.ts
124411
- import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
124651
+ import { existsSync as existsSync13, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
124412
124652
  import { extname as extname4 } from "path";
124413
124653
  init_output();
124414
124654
  var MIME_BY_EXT = {
@@ -124440,10 +124680,10 @@ function parseMaxTokens(value) {
124440
124680
  return n3;
124441
124681
  }
124442
124682
  function loadMedia(file) {
124443
- if (!existsSync12(file)) {
124683
+ if (!existsSync13(file)) {
124444
124684
  error(`File not found: ${file}`);
124445
124685
  }
124446
- const stat = statSync4(file);
124686
+ const stat = statSync5(file);
124447
124687
  if (!stat.isFile()) {
124448
124688
  error(`Not a regular file: ${file}`);
124449
124689
  }
@@ -124451,7 +124691,7 @@ function loadMedia(file) {
124451
124691
  error(`File is empty: ${file}`);
124452
124692
  }
124453
124693
  try {
124454
- return { buffer: readFileSync8(file), mimeType: guessMimeType(file) };
124694
+ return { buffer: readFileSync9(file), mimeType: guessMimeType(file) };
124455
124695
  } catch (err2) {
124456
124696
  const message2 = err2 instanceof Error ? err2.message : "Unknown error";
124457
124697
  return error(`Failed to read ${file}: ${message2}`);
@@ -124526,7 +124766,7 @@ function createSeeCommand() {
124526
124766
  }
124527
124767
 
124528
124768
  // src/commands/send.ts
124529
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "fs";
124769
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
124530
124770
  import { basename as basename5, extname as extname5 } from "path";
124531
124771
  init_source();
124532
124772
  init_config();
@@ -124546,7 +124786,7 @@ function getMediaType2(path) {
124546
124786
  return "document";
124547
124787
  }
124548
124788
  function readFileAsBase64(path) {
124549
- const buffer3 = readFileSync9(path);
124789
+ const buffer3 = readFileSync10(path);
124550
124790
  return buffer3.toString("base64");
124551
124791
  }
124552
124792
  var messageSenders = {
@@ -124567,7 +124807,7 @@ var messageSenders = {
124567
124807
  const { to, media } = options3;
124568
124808
  if (!to || !media)
124569
124809
  return;
124570
- if (!existsSync13(media)) {
124810
+ if (!existsSync14(media)) {
124571
124811
  error(`File not found: ${media}`);
124572
124812
  return;
124573
124813
  }
@@ -125099,9 +125339,9 @@ function pickFilename(mimeType, provider) {
125099
125339
  }
125100
125340
 
125101
125341
  // src/commands/start.ts
125102
- import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
125103
- import { homedir as homedir9 } from "os";
125104
- import { join as join13 } from "path";
125342
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
125343
+ import { homedir as homedir10 } from "os";
125344
+ import { join as join14 } from "path";
125105
125345
  init_config();
125106
125346
  init_output();
125107
125347
  var START_HEALTH_TIMEOUT_MS = 1e4;
@@ -125110,12 +125350,12 @@ async function runStart() {
125110
125350
  pm2NotFoundError();
125111
125351
  }
125112
125352
  const bundlePath = getServerBundlePath();
125113
- if (!existsSync14(bundlePath)) {
125353
+ if (!existsSync15(bundlePath)) {
125114
125354
  bundleNotFoundError(bundlePath);
125115
125355
  }
125116
125356
  const serverConfig = loadServerConfig();
125117
125357
  const apiPort = serverConfig.port;
125118
- mkdirSync8(getPm2LogDir(), { recursive: true });
125358
+ mkdirSync9(getPm2LogDir(), { recursive: true });
125119
125359
  info(`Starting ${PM2_PROCESSES.api} (port ${apiPort})...`);
125120
125360
  const cliConfig = loadConfig();
125121
125361
  const env2 = buildRuntimeEnv(serverConfig, cliConfig);
@@ -125131,11 +125371,11 @@ async function runStart() {
125131
125371
  error(`Failed to start ${PM2_PROCESSES.api} (pm2 exit code ${apiCode})`, undefined, 1);
125132
125372
  return;
125133
125373
  }
125134
- const natsPath = join13(homedir9(), ".omni", "nats-server");
125135
- if (existsSync14(natsPath)) {
125374
+ const natsPath = join14(homedir10(), ".omni", "nats-server");
125375
+ if (existsSync15(natsPath)) {
125136
125376
  info(`Starting ${PM2_PROCESSES.nats}...`);
125137
- const natsDataDir = join13(serverConfig.dataDir, "nats");
125138
- mkdirSync8(natsDataDir, { recursive: true });
125377
+ const natsDataDir = join14(serverConfig.dataDir, "nats");
125378
+ mkdirSync9(natsDataDir, { recursive: true });
125139
125379
  const natsArgs = buildPm2StartArgs({
125140
125380
  kind: "nats",
125141
125381
  script: natsPath,
@@ -126142,9 +126382,9 @@ function createVoiceCommand() {
126142
126382
  const startTime = Date.now();
126143
126383
  let saveDir = "";
126144
126384
  if (opts.save) {
126145
- const { mkdirSync: mkdirSync9 } = await import("fs");
126385
+ const { mkdirSync: mkdirSync10 } = await import("fs");
126146
126386
  saveDir = opts.save;
126147
- mkdirSync9(saveDir, { recursive: true });
126387
+ mkdirSync10(saveDir, { recursive: true });
126148
126388
  info(`Saving audio to: ${saveDir}`);
126149
126389
  }
126150
126390
  ws.onopen = () => {
@@ -126368,17 +126608,17 @@ init_config();
126368
126608
  init_output();
126369
126609
 
126370
126610
  // src/manifest-pin.ts
126371
- import { existsSync as existsSync15, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync8 } from "fs";
126372
- import { homedir as homedir10 } from "os";
126373
- import { join as join14 } from "path";
126611
+ import { existsSync as existsSync16, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync9 } from "fs";
126612
+ import { homedir as homedir11 } from "os";
126613
+ import { join as join15 } from "path";
126374
126614
  var PACKAGE_NAME2 = "@automagik/omni";
126375
- var BUN_GLOBAL_MANIFEST = join14(homedir10(), ".bun", "install", "global", "package.json");
126615
+ var BUN_GLOBAL_MANIFEST = join15(homedir11(), ".bun", "install", "global", "package.json");
126376
126616
  function pinManifestEntry(manifestPath, exactVersion) {
126377
- if (!existsSync15(manifestPath))
126617
+ if (!existsSync16(manifestPath))
126378
126618
  return false;
126379
126619
  let manifest;
126380
126620
  try {
126381
- manifest = JSON.parse(readFileSync10(manifestPath, "utf-8"));
126621
+ manifest = JSON.parse(readFileSync11(manifestPath, "utf-8"));
126382
126622
  } catch {
126383
126623
  return false;
126384
126624
  }
@@ -126404,9 +126644,9 @@ function pinManifestEntry(manifestPath, exactVersion) {
126404
126644
  return false;
126405
126645
  const tmp = `${manifestPath}.tmp.${process.pid}`;
126406
126646
  try {
126407
- writeFileSync8(tmp, `${JSON.stringify(manifest, null, 2)}
126647
+ writeFileSync9(tmp, `${JSON.stringify(manifest, null, 2)}
126408
126648
  `, { mode: 420 });
126409
- renameSync2(tmp, manifestPath);
126649
+ renameSync3(tmp, manifestPath);
126410
126650
  } catch {
126411
126651
  return false;
126412
126652
  }
@@ -47,4 +47,59 @@ export declare function setupCanonicalPgserve(): Promise<string | null>;
47
47
  export declare function resolveCanonicalPgservePreference(isReinstall: boolean, cfg: {
48
48
  databaseUrl: string;
49
49
  }): Promise<boolean>;
50
+ /**
51
+ * Resolve the canonical pgserve data dir from `~/.pgserve/config.json`.
52
+ * pgserve writes `{dataDir, port, registeredAt}` during `pgserve install`.
53
+ * Falls back to `~/.pgserve/data` (pgserve's documented default) when the
54
+ * config file doesn't exist yet.
55
+ *
56
+ * Surfaced in operator output so the migration's destination is explicit:
57
+ * the operator can `du -sh` and `ls` that path post-migration to verify
58
+ * the cluster is on disk where they expect.
59
+ */
60
+ export declare function getCanonicalPgserveDataDir(): string;
61
+ /**
62
+ * Outcome of the dump step. Caller decides whether to proceed with restore.
63
+ */
64
+ export type EmbeddedDumpResult = {
65
+ status: 'no-embedded-data';
66
+ embeddedDir: string;
67
+ } | {
68
+ status: 'embedded-data-invalid';
69
+ embeddedDir: string;
70
+ } | {
71
+ status: 'dumped';
72
+ embeddedDir: string;
73
+ snapshotPath: string;
74
+ bytes: number;
75
+ };
76
+ /**
77
+ * Dump the embedded `omni` database to a gzipped SQL snapshot. Called BEFORE
78
+ * the caller stops omni-api — embedded pgserve must still be live so pg_dump
79
+ * can connect.
80
+ *
81
+ * Returns:
82
+ * - `no-embedded-data` when there's no embedded data dir (fresh install
83
+ * case — caller proceeds with an empty canonical).
84
+ * - `embedded-data-invalid` when the dir exists but isn't a Postgres data
85
+ * dir (corrupt / partial init — caller warns + proceeds empty).
86
+ * - `dumped` with snapshot path + size on success.
87
+ *
88
+ * Throws when pg_dump is missing from PATH or exits non-zero. Caller is
89
+ * responsible for catching and rolling back omni-api to embedded.
90
+ */
91
+ export declare function dumpEmbeddedDb(currentDatabaseUrl: string): Promise<EmbeddedDumpResult>;
92
+ /**
93
+ * Restore a dumped snapshot into the canonical pgserve. Called AFTER the
94
+ * caller has run `pgserve install` and confirmed canonical is reachable
95
+ * at the URL. No-op when the snapshot status was anything but `dumped`.
96
+ *
97
+ * Uses `psql ON_ERROR_STOP=1` so a partial failure surfaces a non-zero exit
98
+ * instead of leaving the canonical DB half-restored. Throws on any psql
99
+ * error — caller rolls back to embedded.
100
+ */
101
+ export declare function restoreSnapshotToCanonical(dump: EmbeddedDumpResult, canonicalDatabaseUrl: string): Promise<{
102
+ status: 'restored' | 'skipped';
103
+ snapshotPath?: string;
104
+ }>;
50
105
  //# sourceMappingURL=canonical-pgserve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"canonical-pgserve.d.ts","sourceRoot":"","sources":["../../src/lib/canonical-pgserve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAqIH;;;;;;GAMG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iCAAiC,CACrD,WAAW,EAAE,OAAO,EACpB,GAAG,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC,OAAO,CAAC,CA6BlB"}
1
+ {"version":3,"file":"canonical-pgserve.d.ts","sourceRoot":"","sources":["../../src/lib/canonical-pgserve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AA2IH;;;;;;GAMG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CASpE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iCAAiC,CACrD,WAAW,EAAE,OAAO,EACpB,GAAG,EAAE;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,GAC3B,OAAO,CAAC,OAAO,CAAC,CA6BlB;AAwDD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CASnD;AAuCD;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,uBAAuB,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnF;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,kBAAkB,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA6D5F;AAED;;;;;;;;GAQG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,kBAAkB,EACxB,oBAAoB,EAAE,MAAM,GAC3B,OAAO,CAAC;IAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAoCpE"}
@@ -21136,6 +21136,9 @@ function createSubscription(options) {
21136
21136
  return {
21137
21137
  id: subscriptionId,
21138
21138
  pattern,
21139
+ isAlive() {
21140
+ return isActive;
21141
+ },
21139
21142
  async unsubscribe() {
21140
21143
  isActive = false;
21141
21144
  abortController.abort();
@@ -28371,13 +28374,16 @@ var init_debounce = __esm(() => {
28371
28374
  // ../core/src/automations/engine.ts
28372
28375
  class AutomationEngine {
28373
28376
  config;
28374
- subscriptions = [];
28377
+ subscriptions = new Map;
28375
28378
  instanceQueues = new Map;
28376
28379
  debounceManagers = new Map;
28377
28380
  automations = [];
28378
28381
  eventBus = null;
28379
28382
  deps;
28380
28383
  logger = null;
28384
+ reconcileTimer = null;
28385
+ reconcileEnabled = false;
28386
+ reconcilePromise = null;
28381
28387
  constructor(config2) {
28382
28388
  this.config = config2;
28383
28389
  this.deps = {
@@ -28394,33 +28400,17 @@ class AutomationEngine {
28394
28400
  callAgent: deps.callAgent
28395
28401
  };
28396
28402
  this.automations = automations.filter((a) => a.enabled);
28397
- const triggerTypes = new Set(this.automations.map((a) => a.triggerEventType));
28398
- for (const eventType of triggerTypes) {
28399
- const durable = `automation-engine-${eventType.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
28400
- const subscription = await eventBus.subscribePattern(`${eventType}.>`, async (event) => {
28401
- await this.handleEvent(event);
28402
- }, {
28403
- durable,
28404
- queue: "automation-engine",
28405
- startFrom: "new",
28406
- maxRetries: 3,
28407
- retryDelayMs: 1000
28408
- });
28409
- this.subscriptions.push(subscription);
28410
- logger4.info(`Subscribed to ${eventType}.*`, { durable });
28411
- }
28412
- for (const automation of this.automations) {
28413
- if (automation.debounce && automation.debounce.mode !== "none") {
28414
- this.setupDebounceManager(automation);
28415
- }
28416
- }
28403
+ await this.reconcileSubscriptions();
28404
+ this.rebuildDebounceManagers();
28405
+ this.startReconcileTimer();
28417
28406
  logger4.info(`Automation engine started with ${this.automations.length} automations`);
28418
28407
  }
28419
28408
  async stop() {
28420
- for (const subscription of this.subscriptions) {
28409
+ this.stopReconcileTimer();
28410
+ for (const subscription of this.subscriptions.values()) {
28421
28411
  await subscription.unsubscribe();
28422
28412
  }
28423
- this.subscriptions = [];
28413
+ this.subscriptions.clear();
28424
28414
  for (const manager of this.debounceManagers.values()) {
28425
28415
  manager.flushAll();
28426
28416
  }
@@ -28431,9 +28421,117 @@ class AutomationEngine {
28431
28421
  this.logger = logger5;
28432
28422
  }
28433
28423
  async reload(automations) {
28434
- await this.stop();
28435
- if (this.eventBus) {
28436
- await this.start(this.eventBus, automations, this.deps);
28424
+ if (!this.eventBus) {
28425
+ this.automations = automations.filter((a) => a.enabled);
28426
+ return;
28427
+ }
28428
+ this.automations = automations.filter((a) => a.enabled);
28429
+ await this.reconcileSubscriptions();
28430
+ this.rebuildDebounceManagers();
28431
+ }
28432
+ async reconcile() {
28433
+ if (!this.eventBus)
28434
+ return;
28435
+ await this.reconcileSubscriptions();
28436
+ }
28437
+ async reconcileSubscriptions() {
28438
+ if (!this.eventBus)
28439
+ return;
28440
+ if (this.reconcilePromise)
28441
+ return this.reconcilePromise;
28442
+ this.reconcilePromise = this.doReconcileSubscriptions().finally(() => {
28443
+ this.reconcilePromise = null;
28444
+ });
28445
+ return this.reconcilePromise;
28446
+ }
28447
+ async doReconcileSubscriptions() {
28448
+ if (!this.eventBus)
28449
+ return;
28450
+ const expectedTriggers = new Set(this.automations.map((a) => a.triggerEventType));
28451
+ for (const [eventType, subscription] of this.subscriptions) {
28452
+ if (!expectedTriggers.has(eventType)) {
28453
+ await subscription.unsubscribe().catch((err) => {
28454
+ logger4.warn("Failed to unsubscribe orphan trigger", { eventType, error: String(err) });
28455
+ });
28456
+ this.subscriptions.delete(eventType);
28457
+ logger4.info(`Unsubscribed from ${eventType}.* (no enabled automations)`);
28458
+ }
28459
+ }
28460
+ for (const eventType of expectedTriggers) {
28461
+ const existing = this.subscriptions.get(eventType);
28462
+ if (existing && this.isSubscriptionAlive(existing)) {
28463
+ continue;
28464
+ }
28465
+ if (existing) {
28466
+ await existing.unsubscribe().catch(() => {});
28467
+ this.subscriptions.delete(eventType);
28468
+ logger4.warn("Replacing dead subscription", { eventType });
28469
+ }
28470
+ const subscription = await this.subscribeForTrigger(eventType);
28471
+ this.subscriptions.set(eventType, subscription);
28472
+ }
28473
+ }
28474
+ async subscribeForTrigger(eventType) {
28475
+ if (!this.eventBus) {
28476
+ throw new Error("Event bus not initialized");
28477
+ }
28478
+ const durable = `automation-engine-${eventType.replace(/[^a-zA-Z0-9_-]/g, "-")}`;
28479
+ const subscription = await this.eventBus.subscribePattern(`${eventType}.>`, async (event) => {
28480
+ await this.handleEvent(event);
28481
+ }, {
28482
+ durable,
28483
+ queue: "automation-engine",
28484
+ startFrom: "new",
28485
+ maxRetries: 3,
28486
+ retryDelayMs: 1000
28487
+ });
28488
+ logger4.info(`Subscribed to ${eventType}.*`, { durable });
28489
+ return subscription;
28490
+ }
28491
+ isSubscriptionAlive(subscription) {
28492
+ return subscription.isAlive ? subscription.isAlive() : true;
28493
+ }
28494
+ rebuildDebounceManagers() {
28495
+ const enabledIds = new Set(this.automations.map((a) => a.id));
28496
+ for (const id of this.debounceManagers.keys()) {
28497
+ if (!enabledIds.has(id)) {
28498
+ this.debounceManagers.get(id)?.flushAll();
28499
+ this.debounceManagers.delete(id);
28500
+ }
28501
+ }
28502
+ for (const automation of this.automations) {
28503
+ if (automation.debounce && automation.debounce.mode !== "none" && !this.debounceManagers.has(automation.id)) {
28504
+ this.setupDebounceManager(automation);
28505
+ }
28506
+ }
28507
+ }
28508
+ startReconcileTimer() {
28509
+ this.stopReconcileTimer();
28510
+ const intervalMs = this.config.reconcileIntervalMs ?? 30000;
28511
+ if (intervalMs <= 0)
28512
+ return;
28513
+ this.reconcileEnabled = true;
28514
+ this.scheduleNextReconcile(intervalMs);
28515
+ }
28516
+ scheduleNextReconcile(intervalMs) {
28517
+ if (!this.reconcileEnabled)
28518
+ return;
28519
+ this.reconcileTimer = setTimeout(() => {
28520
+ this.reconcileSubscriptions().catch((err) => {
28521
+ logger4.error("Reconciler tick failed", { error: String(err) });
28522
+ }).finally(() => {
28523
+ this.scheduleNextReconcile(intervalMs);
28524
+ });
28525
+ }, intervalMs);
28526
+ if (typeof this.reconcileTimer === "object" && this.reconcileTimer && "unref" in this.reconcileTimer) {
28527
+ this.reconcileTimer.unref();
28528
+ }
28529
+ }
28530
+ stopReconcileTimer() {
28531
+ this.reconcileEnabled = false;
28532
+ if (this.reconcileTimer) {
28533
+ clearTimeout(this.reconcileTimer);
28534
+ this.reconcileTimer = null;
28437
28535
  }
28438
28536
  }
28439
28537
  async handleEvent(event) {
@@ -224556,7 +224654,7 @@ var init_sentry_scrub = __esm(() => {
224556
224654
  var require_package8 = __commonJS((exports, module) => {
224557
224655
  module.exports = {
224558
224656
  name: "@omni/api",
224559
- version: "2.260430.16",
224657
+ version: "2.260501.2",
224560
224658
  type: "module",
224561
224659
  exports: {
224562
224660
  ".": {
@@ -283438,11 +283536,12 @@ class FollowUpLifecycleService {
283438
283536
  if (lastInboundCustomerMessageAt) {
283439
283537
  set.lastInboundCustomerMessageAt = lastInboundCustomerMessageAt;
283440
283538
  }
283441
- const result = await this.db.update(chatFollowUpState).set(set).where(and2(eq(chatFollowUpState.chatId, chatId), eq(chatFollowUpState.instanceId, instanceId), isNull2(chatFollowUpState.disarmReason))).returning({ id: chatFollowUpState.id });
283539
+ const reasonGuard = TERMINAL_DISARM_REASONS.has(reason) ? or2(isNull2(chatFollowUpState.disarmReason), notInArray(chatFollowUpState.disarmReason, TERMINAL_OVERRIDE_PROTECTED)) : isNull2(chatFollowUpState.disarmReason);
283540
+ const result = await this.db.update(chatFollowUpState).set(set).where(and2(eq(chatFollowUpState.chatId, chatId), eq(chatFollowUpState.instanceId, instanceId), reasonGuard)).returning({ id: chatFollowUpState.id });
283442
283541
  return { disarmed: result.length > 0 };
283443
283542
  }
283444
283543
  }
283445
- var log85, TERMINAL_DISARM_REASONS;
283544
+ var log85, TERMINAL_DISARM_REASONS, TERMINAL_OVERRIDE_PROTECTED;
283446
283545
  var init_follow_up_lifecycle = __esm(() => {
283447
283546
  init_src();
283448
283547
  init_src5();
@@ -283454,6 +283553,7 @@ var init_follow_up_lifecycle = __esm(() => {
283454
283553
  "archived",
283455
283554
  "window_expired"
283456
283555
  ]);
283556
+ TERMINAL_OVERRIDE_PROTECTED = [...TERMINAL_DISARM_REASONS, "contact_closed"];
283457
283557
  });
283458
283558
 
283459
283559
  // ../api/src/services/follow-up-sweeper.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260430.16",
3
+ "version": "2.260501.2",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,15 +51,15 @@
51
51
  "qrcode-terminal": "^0.12.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@omni/api": "2.260430.15",
55
- "@omni/channel-discord": "2.260430.15",
56
- "@omni/channel-gupshup": "2.260430.15",
57
- "@omni/channel-sdk": "2.260430.15",
58
- "@omni/channel-slack": "2.260430.15",
59
- "@omni/channel-telegram": "2.260430.15",
60
- "@omni/channel-whatsapp": "2.260430.15",
61
- "@omni/core": "2.260430.15",
62
- "@omni/sdk": "2.260430.15",
54
+ "@omni/api": "2.260501.1",
55
+ "@omni/channel-discord": "2.260501.1",
56
+ "@omni/channel-gupshup": "2.260501.1",
57
+ "@omni/channel-sdk": "2.260501.1",
58
+ "@omni/channel-slack": "2.260501.1",
59
+ "@omni/channel-telegram": "2.260501.1",
60
+ "@omni/channel-whatsapp": "2.260501.1",
61
+ "@omni/core": "2.260501.1",
62
+ "@omni/sdk": "2.260501.1",
63
63
  "@types/node": "^22.10.3",
64
64
  "@types/qrcode-terminal": "^0.12.2",
65
65
  "typescript": "^5.7.3"