@automagik/omni 2.260704.1 → 2.260704.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.
@@ -0,0 +1,22 @@
1
+ -- Per-instance first-party cross-instance opt-in.
2
+ --
3
+ -- Adds `instances.allow_first_party` (default false). When true, the agent
4
+ -- dispatcher will NOT drop inbound messages whose sender phone matches another
5
+ -- active instance's owner. This lets a user's "assistant" instance reply to
6
+ -- messages sent from their own personal number (which is another instance's
7
+ -- owner) instead of silently dropping them via loop protection.
8
+ --
9
+ -- Default false preserves current loop-protection behavior for every existing
10
+ -- instance. Opt in per-instance via:
11
+ --
12
+ -- omni instances update <id> --allow-first-party
13
+ --
14
+ -- Does NOT affect the separate "message from self" self-skip — an instance
15
+ -- still never replies to its own outbound. See `isFirstPartyInstanceSender`
16
+ -- and the gate in packages/api/src/plugins/agent-dispatcher.ts.
17
+ --
18
+ -- Hand-written (not `drizzle-kit generate`) to avoid the gupshup column
19
+ -- rename prompts that block non-interactive runs in this repo.
20
+
21
+ ALTER TABLE "instances"
22
+ ADD COLUMN IF NOT EXISTS "allow_first_party" boolean DEFAULT false NOT NULL;
@@ -267,6 +267,13 @@
267
267
  "when": 1777950000000,
268
268
  "tag": "0037_message_debounce_max_wait",
269
269
  "breakpoints": true
270
+ },
271
+ {
272
+ "idx": 38,
273
+ "version": "7",
274
+ "when": 1778040000000,
275
+ "tag": "0038_instances_allow_first_party",
276
+ "breakpoints": true
270
277
  }
271
278
  ]
272
279
  }
@@ -1 +1 @@
1
- {"version":3,"file":"instances.d.ts","sourceRoot":"","sources":["../../src/commands/instances.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkMpC,wBAAgB,sBAAsB,IAAI,OAAO,CAwuChD"}
1
+ {"version":3,"file":"instances.d.ts","sourceRoot":"","sources":["../../src/commands/instances.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqMpC,wBAAgB,sBAAsB,IAAI,OAAO,CA0vChD"}
package/dist/index.js CHANGED
@@ -71672,6 +71672,7 @@ var init_schema2 = __esm(() => {
71672
71672
  followUpConfig: jsonb("follow_up_config").$type(),
71673
71673
  bridgeTmuxSession: text("bridge_tmux_session"),
71674
71674
  requireGenieSignature: boolean("require_genie_signature").notNull().default(false),
71675
+ allowFirstParty: boolean("allow_first_party").notNull().default(false),
71675
71676
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
71676
71677
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
71677
71678
  }, (table3) => ({
@@ -125009,7 +125010,7 @@ import { fileURLToPath } from "url";
125009
125010
  // package.json
125010
125011
  var package_default = {
125011
125012
  name: "@automagik/omni",
125012
- version: "2.260704.1",
125013
+ version: "2.260704.2",
125013
125014
  description: "LLM-optimized CLI for Omni",
125014
125015
  type: "module",
125015
125016
  bin: {
@@ -132572,6 +132573,7 @@ function applyMiscFields(body, opts) {
132572
132573
  setBool(body, "twilioValidateSignature", opts.twilioValidateSignature);
132573
132574
  setVal(body, "bridgeTmuxSession", opts.bridgeTmuxSession);
132574
132575
  setBool(body, "requireGenieSignature", opts.requireGenieSignature);
132576
+ setBool(body, "allowFirstParty", opts.allowFirstParty);
132575
132577
  if (opts.triggerEvents !== undefined) {
132576
132578
  const raw2 = opts.triggerEvents;
132577
132579
  body.triggerEvents = raw2 === "null" ? null : raw2.split(",").map((s) => s.trim());
@@ -132682,7 +132684,7 @@ function createInstancesCommand() {
132682
132684
  error(`Failed to get instance: ${message}`, undefined, 3);
132683
132685
  }
132684
132686
  });
132685
- instances.command("create").description("Create a new instance (supports all API fields)").requiredOption("--name <name>", "Instance name").requiredOption("--channel <type>", `Channel type (${VALID_CHANNELS2.join(", ")})`).option("--agent-fk-id <uuid>", 'Agent FK UUID (references agents table, use "null" to clear). When set without --reply-filter-mode, reply filter defaults to {mode:"all", onDm:true} so messages are dispatched instead of silently dropped (omni#443).').option("--agent-provider <id>", "Agent provider ID").option("--agent <id>", "Agent ID").option("--agent-type <type>", "Agent type: agent, team, or workflow").option("--agent-timeout <seconds>", "Agent timeout in seconds", (v) => Number.parseInt(v, 10)).option("--agent-stream-mode", "Enable streaming responses").option("--agent-session-strategy <strategy>", "Session strategy: per_user, per_chat, per_user_per_chat").option("--agent-prefix-sender-name", "Prefix messages with sender name").option("--no-agent-prefix-sender-name", "Disable sender name prefix").option("--agent-wait-for-media", "Wait for media processing before dispatch").option("--no-agent-wait-for-media", "Dispatch immediately without waiting for media").option("--agent-send-media-path", "Include file path in formatted media text").option("--no-agent-send-media-path", "Exclude file path from formatted media text").option("--agent-send-media-path-types <types>", "Content types that receive file path (comma-separated: image,video,document)").option("--reply-filter-mode <mode>", "Reply filter: all or filtered").option("--reply-on-dm", "Reply to DMs").option("--no-reply-on-dm", "Ignore DMs").option("--reply-on-mention", "Reply when @mentioned").option("--no-reply-on-mention", "Ignore @mentions").option("--reply-on-reply", "Reply when message is reply to bot").option("--no-reply-on-reply", "Ignore replies").option("--reply-on-name", "Reply when bot name appears in text").option("--no-reply-on-name", "Ignore name matches").option("--reply-name-patterns <patterns>", "Custom name patterns (comma-separated)").option("--enable-auto-split", "Split responses on double newlines").option("--no-enable-auto-split", "Disable auto-split").option("--message-format-mode <mode>", "Format mode: convert or passthrough").option("--debounce-mode <mode>", "Debounce mode: disabled, fixed, randomized, or presence").option("--debounce-min <ms>", "Minimum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max <ms>", "Maximum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-restart-on-typing", "Restart debounce timer on typing").option("--debounce-group <ms>", "Group chat debounce in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max-wait <ms>", "Presence-mode hard cap in ms (flush even under continuous typing)", (v) => Number.parseInt(v, 10)).option("--split-delay-mode <mode>", "Split delay mode: disabled, fixed, or randomized").option("--split-delay-fixed <ms>", "Fixed delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-min <ms>", "Minimum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-max <ms>", "Maximum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--agent-gate", "Enable LLM response gate").option("--agent-gate-model <model>", "Model for response gate").option("--agent-gate-prompt <prompt>", "Custom gate prompt").option("--tts-voice <id>", "ElevenLabs voice ID").option("--tts-model <id>", "ElevenLabs model ID").option("--read-receipts <mode>", "Read receipts mode: on, off, or exclude-self").option("--access-mode <mode>", "Access mode: disabled, blocklist, or allowlist").option("--reaction-ack <mode>", "Reaction ack mode (on|off)").option("--reaction-ack-emoji <json>", "Per-channel emoji map as JSON").option("--ack-timeout <ms>", "Ack timeout in milliseconds", (v) => Number.parseInt(v, 10)).option("--agent-stalled-timeout-ms <ms>", "Idle threshold in ms before the internal turn.stalled event fires (no channel message is ever sent)", (v) => Number.parseInt(v, 10)).option("--token <token>", "Generic bot token (auto-resolves to channel-specific field)").option("--telegram-token <token>", "Telegram bot token").option("--discord-token <token>", "Discord bot token").option("--slack-bot-token <token>", "Slack bot token").option("--slack-app-token <token>", "Slack app token").option("--gupshup-callback-url <url>", "Gupshup Custom Integration callback URL").option("--gupshup-auth-token <token>", "Gupshup Custom Integration auth token").option("--gupshup-event-id <id>", "Gupshup event ID (default: nx_omni_agent_reply)").option("--gupshup-webhook-verify-token <token>", "Gupshup webhook verify token").option("--twilio-account-sid <sid>", "Twilio Account SID").option("--twilio-auth-token <token>", "Twilio Auth Token").option("--twilio-from <address>", "Twilio WhatsApp sender address (whatsapp:+E164)").option("--twilio-messaging-service-sid <sid>", "Twilio Messaging Service SID").option("--twilio-status-callback-url <url>", "Twilio outbound status callback URL").option("--twilio-webhook-url <url>", "Public Twilio webhook URL for signature validation").option("--twilio-validate-signature", "Validate X-Twilio-Signature on webhooks").option("--no-twilio-validate-signature", "Disable X-Twilio-Signature validation").option("--bridge-tmux-session <name>", 'Tmux session name the genie bridge spawns into for this instance (propagated as GENIE_TMUX_SESSION via NATS). Use "null" to clear.').option("--require-genie-signature", "Require a verified X-Genie-Signature on requests targeting this instance. Bearer-only requests will be rejected with 401.").option("--no-require-genie-signature", "Allow bearer-only requests targeting this instance (default).").option("--is-default", "Set as default instance for channel").action(async (options) => {
132687
+ instances.command("create").description("Create a new instance (supports all API fields)").requiredOption("--name <name>", "Instance name").requiredOption("--channel <type>", `Channel type (${VALID_CHANNELS2.join(", ")})`).option("--agent-fk-id <uuid>", 'Agent FK UUID (references agents table, use "null" to clear). When set without --reply-filter-mode, reply filter defaults to {mode:"all", onDm:true} so messages are dispatched instead of silently dropped (omni#443).').option("--agent-provider <id>", "Agent provider ID").option("--agent <id>", "Agent ID").option("--agent-type <type>", "Agent type: agent, team, or workflow").option("--agent-timeout <seconds>", "Agent timeout in seconds", (v) => Number.parseInt(v, 10)).option("--agent-stream-mode", "Enable streaming responses").option("--agent-session-strategy <strategy>", "Session strategy: per_user, per_chat, per_user_per_chat").option("--agent-prefix-sender-name", "Prefix messages with sender name").option("--no-agent-prefix-sender-name", "Disable sender name prefix").option("--agent-wait-for-media", "Wait for media processing before dispatch").option("--no-agent-wait-for-media", "Dispatch immediately without waiting for media").option("--agent-send-media-path", "Include file path in formatted media text").option("--no-agent-send-media-path", "Exclude file path from formatted media text").option("--agent-send-media-path-types <types>", "Content types that receive file path (comma-separated: image,video,document)").option("--reply-filter-mode <mode>", "Reply filter: all or filtered").option("--reply-on-dm", "Reply to DMs").option("--no-reply-on-dm", "Ignore DMs").option("--reply-on-mention", "Reply when @mentioned").option("--no-reply-on-mention", "Ignore @mentions").option("--reply-on-reply", "Reply when message is reply to bot").option("--no-reply-on-reply", "Ignore replies").option("--reply-on-name", "Reply when bot name appears in text").option("--no-reply-on-name", "Ignore name matches").option("--reply-name-patterns <patterns>", "Custom name patterns (comma-separated)").option("--enable-auto-split", "Split responses on double newlines").option("--no-enable-auto-split", "Disable auto-split").option("--message-format-mode <mode>", "Format mode: convert or passthrough").option("--debounce-mode <mode>", "Debounce mode: disabled, fixed, randomized, or presence").option("--debounce-min <ms>", "Minimum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max <ms>", "Maximum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-restart-on-typing", "Restart debounce timer on typing").option("--debounce-group <ms>", "Group chat debounce in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max-wait <ms>", "Presence-mode hard cap in ms (flush even under continuous typing)", (v) => Number.parseInt(v, 10)).option("--split-delay-mode <mode>", "Split delay mode: disabled, fixed, or randomized").option("--split-delay-fixed <ms>", "Fixed delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-min <ms>", "Minimum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-max <ms>", "Maximum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--agent-gate", "Enable LLM response gate").option("--agent-gate-model <model>", "Model for response gate").option("--agent-gate-prompt <prompt>", "Custom gate prompt").option("--tts-voice <id>", "ElevenLabs voice ID").option("--tts-model <id>", "ElevenLabs model ID").option("--read-receipts <mode>", "Read receipts mode: on, off, or exclude-self").option("--access-mode <mode>", "Access mode: disabled, blocklist, or allowlist").option("--reaction-ack <mode>", "Reaction ack mode (on|off)").option("--reaction-ack-emoji <json>", "Per-channel emoji map as JSON").option("--ack-timeout <ms>", "Ack timeout in milliseconds", (v) => Number.parseInt(v, 10)).option("--agent-stalled-timeout-ms <ms>", "Idle threshold in ms before the internal turn.stalled event fires (no channel message is ever sent)", (v) => Number.parseInt(v, 10)).option("--token <token>", "Generic bot token (auto-resolves to channel-specific field)").option("--telegram-token <token>", "Telegram bot token").option("--discord-token <token>", "Discord bot token").option("--slack-bot-token <token>", "Slack bot token").option("--slack-app-token <token>", "Slack app token").option("--gupshup-callback-url <url>", "Gupshup Custom Integration callback URL").option("--gupshup-auth-token <token>", "Gupshup Custom Integration auth token").option("--gupshup-event-id <id>", "Gupshup event ID (default: nx_omni_agent_reply)").option("--gupshup-webhook-verify-token <token>", "Gupshup webhook verify token").option("--twilio-account-sid <sid>", "Twilio Account SID").option("--twilio-auth-token <token>", "Twilio Auth Token").option("--twilio-from <address>", "Twilio WhatsApp sender address (whatsapp:+E164)").option("--twilio-messaging-service-sid <sid>", "Twilio Messaging Service SID").option("--twilio-status-callback-url <url>", "Twilio outbound status callback URL").option("--twilio-webhook-url <url>", "Public Twilio webhook URL for signature validation").option("--twilio-validate-signature", "Validate X-Twilio-Signature on webhooks").option("--no-twilio-validate-signature", "Disable X-Twilio-Signature validation").option("--bridge-tmux-session <name>", 'Tmux session name the genie bridge spawns into for this instance (propagated as GENIE_TMUX_SESSION via NATS). Use "null" to clear.').option("--require-genie-signature", "Require a verified X-Genie-Signature on requests targeting this instance. Bearer-only requests will be rejected with 401.").option("--no-require-genie-signature", "Allow bearer-only requests targeting this instance (default).").option("--allow-first-party", "Process (do not drop) inbound messages whose sender matches another active instance owner. Lets this instance reply to messages from your own personal number.").option("--no-allow-first-party", "Drop inbound messages whose sender matches another active instance owner (default, loop-protection).").option("--is-default", "Set as default instance for channel").action(async (options) => {
132686
132688
  const channel = options.channel;
132687
132689
  if (!VALID_CHANNELS2.includes(channel)) {
132688
132690
  error(`Invalid channel: ${channel}`, { validChannels: VALID_CHANNELS2 });
@@ -132950,7 +132952,7 @@ function createInstancesCommand() {
132950
132952
  throw new Error(err?.error?.message ?? `HTTP ${response.status}`);
132951
132953
  }
132952
132954
  }
132953
- instances.command("update <id>").description("Update an instance (supports all API fields)").option("--name <name>", "Instance name").option("--is-default", "Set as default instance for channel").option("--no-is-default", "Unset as default instance for channel").option("--agent-fk-id <uuid>", 'Agent FK UUID (references agents table, use "null" to clear). When assigning an agent on an instance with no reply filter, the filter defaults to {mode:"all", onDm:true} so messages are dispatched instead of silently dropped (omni#443).').option("--agent-provider <id>", 'Agent provider ID (use "null" to clear)').option("--agent <id>", 'Agent ID (use "null" to clear)').option("--agent-type <type>", "Agent type: agent, team, or workflow").option("--agent-timeout <seconds>", "Agent timeout in seconds", (v) => Number.parseInt(v, 10)).option("--agent-stream-mode", "Enable streaming responses").option("--no-agent-stream-mode", "Disable streaming responses").option("--agent-session-strategy <strategy>", "Session strategy: per_user, per_chat, per_user_per_chat").option("--agent-prefix-sender-name", "Prefix messages with sender name").option("--no-agent-prefix-sender-name", "Disable sender name prefix").option("--agent-wait-for-media", "Wait for media processing before dispatch").option("--no-agent-wait-for-media", "Dispatch immediately without waiting for media").option("--agent-send-media-path", "Include file path in formatted media text").option("--no-agent-send-media-path", "Exclude file path from formatted media text").option("--agent-send-media-path-types <types>", "Content types that receive file path (comma-separated: image,video,document)").option("--reply-filter-mode <mode>", "Reply filter: all or filtered").option("--reply-on-dm", "Reply to DMs (requires --reply-filter-mode filtered)").option("--no-reply-on-dm", "Ignore DMs").option("--reply-on-mention", "Reply when @mentioned").option("--no-reply-on-mention", "Ignore @mentions").option("--reply-on-reply", "Reply when message is reply to bot").option("--no-reply-on-reply", "Ignore replies").option("--reply-on-name", "Reply when bot name appears in text").option("--no-reply-on-name", "Ignore name matches").option("--reply-name-patterns <patterns>", "Custom name patterns (comma-separated)").option("--clear-reply-filter", "Remove reply filter (set to null)").option("--enable-auto-split", "Split responses on double newlines").option("--no-enable-auto-split", "Disable auto-split").option("--message-format-mode <mode>", "Format mode: convert or passthrough").option("--debounce-mode <mode>", "Debounce mode: disabled, fixed, randomized, or presence").option("--debounce-min <ms>", "Minimum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max <ms>", "Maximum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-restart-on-typing", "Restart debounce timer on typing").option("--no-debounce-restart-on-typing", "Do not restart debounce on typing").option("--debounce-group <ms>", 'Group chat debounce in ms (use "null" to inherit)', (v) => v === "null" ? null : Number.parseInt(v, 10)).option("--debounce-max-wait <ms>", 'Presence-mode hard cap in ms (use "null" to inherit)', (v) => v === "null" ? null : Number.parseInt(v, 10)).option("--split-delay-mode <mode>", "Split delay mode: disabled, fixed, or randomized").option("--split-delay-fixed <ms>", "Fixed delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-min <ms>", "Minimum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-max <ms>", "Maximum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--agent-gate", "Enable LLM response gate").option("--no-agent-gate", "Disable LLM response gate").option("--agent-gate-model <model>", 'Model for response gate (use "null" for default)').option("--agent-gate-prompt <prompt>", 'Custom gate prompt (use "null" for default)').option("--tts-voice <id>", 'ElevenLabs voice ID (use "null" to clear)').option("--tts-model <id>", 'ElevenLabs model ID (use "null" to clear)').option("--read-receipts <mode>", "Read receipts mode: on, off, or exclude-self").option("--access-mode <mode>", "Access mode: disabled, blocklist, or allowlist").option("--reaction-ack <mode>", "Reaction ack mode (on|off)").option("--reaction-ack-emoji <json>", "Per-channel emoji map as JSON").option("--ack-timeout <ms>", "Ack timeout in milliseconds", (v) => Number.parseInt(v, 10)).option("--agent-stalled-timeout-ms <ms>", "Idle threshold in ms before the internal turn.stalled event fires (no channel message is ever sent)", (v) => Number.parseInt(v, 10)).option("--token <token>", "Generic bot token (auto-resolves to channel-specific field)").option("--telegram-token <token>", 'Telegram bot token (use "null" to clear)').option("--discord-token <token>", 'Discord bot token (use "null" to clear)').option("--slack-bot-token <token>", 'Slack bot token (use "null" to clear)').option("--slack-app-token <token>", 'Slack app token (use "null" to clear)').option("--twilio-account-sid <sid>", 'Twilio Account SID (use "null" to clear)').option("--twilio-auth-token <token>", 'Twilio Auth Token (use "null" to clear)').option("--twilio-from <address>", 'Twilio WhatsApp sender address (use "null" to clear)').option("--twilio-messaging-service-sid <sid>", 'Twilio Messaging Service SID (use "null" to clear)').option("--twilio-status-callback-url <url>", 'Twilio outbound status callback URL (use "null" to clear)').option("--twilio-webhook-url <url>", 'Public Twilio webhook URL for signature validation (use "null" to clear)').option("--twilio-validate-signature", "Validate X-Twilio-Signature on webhooks").option("--no-twilio-validate-signature", "Disable X-Twilio-Signature validation").option("--trigger-events <events>", 'Trigger events (comma-separated, use "null" to clear)').option("--profile-name <name>", "Update WhatsApp display name (push name)").option("--bridge-tmux-session <name>", 'Tmux session name the genie bridge spawns into for this instance (propagated as GENIE_TMUX_SESSION via NATS). Use "null" to clear.').option("--require-genie-signature", "Require a verified X-Genie-Signature on requests targeting this instance. Bearer-only requests will be rejected with 401.").option("--no-require-genie-signature", "Allow bearer-only requests targeting this instance (default).").action(async (rawId, options) => {
132955
+ instances.command("update <id>").description("Update an instance (supports all API fields)").option("--name <name>", "Instance name").option("--is-default", "Set as default instance for channel").option("--no-is-default", "Unset as default instance for channel").option("--agent-fk-id <uuid>", 'Agent FK UUID (references agents table, use "null" to clear). When assigning an agent on an instance with no reply filter, the filter defaults to {mode:"all", onDm:true} so messages are dispatched instead of silently dropped (omni#443).').option("--agent-provider <id>", 'Agent provider ID (use "null" to clear)').option("--agent <id>", 'Agent ID (use "null" to clear)').option("--agent-type <type>", "Agent type: agent, team, or workflow").option("--agent-timeout <seconds>", "Agent timeout in seconds", (v) => Number.parseInt(v, 10)).option("--agent-stream-mode", "Enable streaming responses").option("--no-agent-stream-mode", "Disable streaming responses").option("--agent-session-strategy <strategy>", "Session strategy: per_user, per_chat, per_user_per_chat").option("--agent-prefix-sender-name", "Prefix messages with sender name").option("--no-agent-prefix-sender-name", "Disable sender name prefix").option("--agent-wait-for-media", "Wait for media processing before dispatch").option("--no-agent-wait-for-media", "Dispatch immediately without waiting for media").option("--agent-send-media-path", "Include file path in formatted media text").option("--no-agent-send-media-path", "Exclude file path from formatted media text").option("--agent-send-media-path-types <types>", "Content types that receive file path (comma-separated: image,video,document)").option("--reply-filter-mode <mode>", "Reply filter: all or filtered").option("--reply-on-dm", "Reply to DMs (requires --reply-filter-mode filtered)").option("--no-reply-on-dm", "Ignore DMs").option("--reply-on-mention", "Reply when @mentioned").option("--no-reply-on-mention", "Ignore @mentions").option("--reply-on-reply", "Reply when message is reply to bot").option("--no-reply-on-reply", "Ignore replies").option("--reply-on-name", "Reply when bot name appears in text").option("--no-reply-on-name", "Ignore name matches").option("--reply-name-patterns <patterns>", "Custom name patterns (comma-separated)").option("--clear-reply-filter", "Remove reply filter (set to null)").option("--enable-auto-split", "Split responses on double newlines").option("--no-enable-auto-split", "Disable auto-split").option("--message-format-mode <mode>", "Format mode: convert or passthrough").option("--debounce-mode <mode>", "Debounce mode: disabled, fixed, randomized, or presence").option("--debounce-min <ms>", "Minimum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-max <ms>", "Maximum debounce delay in ms", (v) => Number.parseInt(v, 10)).option("--debounce-restart-on-typing", "Restart debounce timer on typing").option("--no-debounce-restart-on-typing", "Do not restart debounce on typing").option("--debounce-group <ms>", 'Group chat debounce in ms (use "null" to inherit)', (v) => v === "null" ? null : Number.parseInt(v, 10)).option("--debounce-max-wait <ms>", 'Presence-mode hard cap in ms (use "null" to inherit)', (v) => v === "null" ? null : Number.parseInt(v, 10)).option("--split-delay-mode <mode>", "Split delay mode: disabled, fixed, or randomized").option("--split-delay-fixed <ms>", "Fixed delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-min <ms>", "Minimum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--split-delay-max <ms>", "Maximum delay between split chunks in ms", (v) => Number.parseInt(v, 10)).option("--agent-gate", "Enable LLM response gate").option("--no-agent-gate", "Disable LLM response gate").option("--agent-gate-model <model>", 'Model for response gate (use "null" for default)').option("--agent-gate-prompt <prompt>", 'Custom gate prompt (use "null" for default)').option("--tts-voice <id>", 'ElevenLabs voice ID (use "null" to clear)').option("--tts-model <id>", 'ElevenLabs model ID (use "null" to clear)').option("--read-receipts <mode>", "Read receipts mode: on, off, or exclude-self").option("--access-mode <mode>", "Access mode: disabled, blocklist, or allowlist").option("--reaction-ack <mode>", "Reaction ack mode (on|off)").option("--reaction-ack-emoji <json>", "Per-channel emoji map as JSON").option("--ack-timeout <ms>", "Ack timeout in milliseconds", (v) => Number.parseInt(v, 10)).option("--agent-stalled-timeout-ms <ms>", "Idle threshold in ms before the internal turn.stalled event fires (no channel message is ever sent)", (v) => Number.parseInt(v, 10)).option("--token <token>", "Generic bot token (auto-resolves to channel-specific field)").option("--telegram-token <token>", 'Telegram bot token (use "null" to clear)').option("--discord-token <token>", 'Discord bot token (use "null" to clear)').option("--slack-bot-token <token>", 'Slack bot token (use "null" to clear)').option("--slack-app-token <token>", 'Slack app token (use "null" to clear)').option("--twilio-account-sid <sid>", 'Twilio Account SID (use "null" to clear)').option("--twilio-auth-token <token>", 'Twilio Auth Token (use "null" to clear)').option("--twilio-from <address>", 'Twilio WhatsApp sender address (use "null" to clear)').option("--twilio-messaging-service-sid <sid>", 'Twilio Messaging Service SID (use "null" to clear)').option("--twilio-status-callback-url <url>", 'Twilio outbound status callback URL (use "null" to clear)').option("--twilio-webhook-url <url>", 'Public Twilio webhook URL for signature validation (use "null" to clear)').option("--twilio-validate-signature", "Validate X-Twilio-Signature on webhooks").option("--no-twilio-validate-signature", "Disable X-Twilio-Signature validation").option("--trigger-events <events>", 'Trigger events (comma-separated, use "null" to clear)').option("--profile-name <name>", "Update WhatsApp display name (push name)").option("--bridge-tmux-session <name>", 'Tmux session name the genie bridge spawns into for this instance (propagated as GENIE_TMUX_SESSION via NATS). Use "null" to clear.').option("--require-genie-signature", "Require a verified X-Genie-Signature on requests targeting this instance. Bearer-only requests will be rejected with 401.").option("--no-require-genie-signature", "Allow bearer-only requests targeting this instance (default).").option("--allow-first-party", "Process (do not drop) inbound messages whose sender matches another active instance owner. Lets this instance reply to messages from your own personal number.").option("--no-allow-first-party", "Drop inbound messages whose sender matches another active instance owner (default, loop-protection).").action(async (rawId, options) => {
132954
132956
  const client = getClient();
132955
132957
  try {
132956
132958
  const id = await resolveInstanceId(rawId);
@@ -225489,7 +225489,7 @@ var init_sentry_scrub = __esm(() => {
225489
225489
  var require_package7 = __commonJS((exports, module) => {
225490
225490
  module.exports = {
225491
225491
  name: "@omni/api",
225492
- version: "2.260704.1",
225492
+ version: "2.260704.2",
225493
225493
  type: "module",
225494
225494
  exports: {
225495
225495
  ".": {
@@ -278057,6 +278057,7 @@ var init_schema2 = __esm(() => {
278057
278057
  followUpConfig: jsonb("follow_up_config").$type(),
278058
278058
  bridgeTmuxSession: text("bridge_tmux_session"),
278059
278059
  requireGenieSignature: boolean("require_genie_signature").notNull().default(false),
278060
+ allowFirstParty: boolean("allow_first_party").notNull().default(false),
278060
278061
  createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
278061
278062
  updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
278062
278063
  }, (table3) => ({
@@ -316254,7 +316255,7 @@ var init_audio2 = __esm(() => {
316254
316255
  }
316255
316256
  if (provider === "gemini") {
316256
316257
  return [
316257
- () => this.transcribeWithGemini(language, options, preferredModel ?? GEMINI_AUDIO_MODEL, getNormalizedAudio),
316258
+ () => this.transcribeWithGemini(language, options, this.resolveGeminiAudioModel(options?.model), getNormalizedAudio),
316258
316259
  () => this.transcribeWithOpenAiAudioChat(language, options, OPENAI_AUDIO_CHAT_MODEL, getNormalizedAudio),
316259
316260
  () => this.transcribeWithGroq(language, getNormalizedAudio)
316260
316261
  ];
@@ -316264,6 +316265,11 @@ var init_audio2 = __esm(() => {
316264
316265
  () => this.transcribeWithOpenAiTranscriptions(language, options, preferredModel ?? OPENAI_TRANSCRIBE_MODEL, getNormalizedAudio)
316265
316266
  ];
316266
316267
  }
316268
+ resolveGeminiAudioModel(optionModel) {
316269
+ if (optionModel?.startsWith("gemini"))
316270
+ return optionModel;
316271
+ return this.config.geminiAudioModel ?? GEMINI_AUDIO_MODEL;
316272
+ }
316267
316273
  async transcribeWithOpenAiAudioChat(language, options, model, getNormalizedAudio) {
316268
316274
  if (!this.config.openaiApiKey) {
316269
316275
  return this.createFailedResult("OpenAI client not configured (missing API key)", "openai", model);
@@ -333107,6 +333113,7 @@ function createMediaProcessingService(config4) {
333107
333113
  geminiApiKey: config4?.geminiApiKey ?? process.env.GEMINI_API_KEY ?? process.env.GOOGLE_API_KEY,
333108
333114
  audioProvider: config4?.audioProvider ?? process.env.STT_PROVIDER,
333109
333115
  audioModel: config4?.audioModel ?? process.env.OPENAI_STT_MODEL,
333116
+ geminiAudioModel: config4?.geminiAudioModel ?? process.env.GEMINI_STT_MODEL,
333110
333117
  audioPrompt: config4?.audioPrompt,
333111
333118
  audioGlossary: config4?.audioGlossary,
333112
333119
  defaultLanguage: config4?.defaultLanguage ?? process.env.DEFAULT_LANGUAGE ?? "pt",
@@ -333433,13 +333440,23 @@ class BatchJobService {
333433
333440
  async createMediaService() {
333434
333441
  let config4;
333435
333442
  if (this.settings) {
333436
- const [groqApiKey, openaiApiKey, geminiApiKey, defaultLanguage, audioProvider, audioModel, audioPrompt] = await Promise.all([
333443
+ const [
333444
+ groqApiKey,
333445
+ openaiApiKey,
333446
+ geminiApiKey,
333447
+ defaultLanguage,
333448
+ audioProvider,
333449
+ audioModel,
333450
+ geminiAudioModel,
333451
+ audioPrompt
333452
+ ] = await Promise.all([
333437
333453
  this.settings.getSecret("groq.api_key", "GROQ_API_KEY"),
333438
333454
  this.settings.getSecret("openai.api_key", "OPENAI_API_KEY"),
333439
333455
  this.settings.getSecret("gemini.api_key", "GEMINI_API_KEY"),
333440
333456
  this.settings.getString("media.default_language", "DEFAULT_LANGUAGE", "pt"),
333441
333457
  this.settings.getString("stt.provider", "STT_PROVIDER", "openai"),
333442
333458
  this.settings.getString("stt.openai.model", "OPENAI_STT_MODEL", "gpt-audio-mini"),
333459
+ this.settings.getString("stt.gemini.model", "GEMINI_STT_MODEL", GEMINI_AUDIO_MODEL),
333443
333460
  this.settings.getString("prompt.audio_transcription")
333444
333461
  ]);
333445
333462
  config4 = {
@@ -333449,6 +333466,7 @@ class BatchJobService {
333449
333466
  defaultLanguage: defaultLanguage ?? "pt",
333450
333467
  audioProvider: audioProvider ?? "openai",
333451
333468
  audioModel: audioModel ?? "gpt-audio-mini",
333469
+ geminiAudioModel: geminiAudioModel ?? GEMINI_AUDIO_MODEL,
333452
333470
  audioPrompt: audioPrompt ?? undefined
333453
333471
  };
333454
333472
  }
@@ -341135,7 +341153,7 @@ async function shouldProcessMessage3(agentRunner, accessService, chatsService, m
341135
341153
  return null;
341136
341154
  }
341137
341155
  const activeOwnerIdentifiers = await listActiveOwnerIdentifiers(db2);
341138
- if (isFirstPartyInstanceSender(payload, instance4.ownerIdentifier, activeOwnerIdentifiers)) {
341156
+ if (!instance4.allowFirstParty && isFirstPartyInstanceSender(payload, instance4.ownerIdentifier, activeOwnerIdentifiers)) {
341139
341157
  log100.info("Skipping first-party cross-instance message", {
341140
341158
  instanceId: instance4.id
341141
341159
  });
@@ -354041,7 +354059,8 @@ var init_instances3 = __esm(() => {
354041
354059
  ackTimeoutMs: exports_external.number().int().min(0).max(120000).default(30000).describe("Ack timeout in milliseconds (max 120000)"),
354042
354060
  agentStalledTimeoutMs: exports_external.number().int().min(0).default(600000).describe("Idle threshold in ms before the internal turn.stalled event fires (no channel message is ever sent)"),
354043
354061
  bridgeTmuxSession: exports_external.string().min(1).max(64).regex(/^[a-zA-Z0-9_.-]+$/, "Tmux session name must be alphanumeric with `_`, `-`, or `.` (no `/`, `:`, or whitespace).").optional().nullable().describe("Tmux session name the genie bridge will spawn into for this instance. Propagated via NATS env as GENIE_TMUX_SESSION. Null clears the override (genie falls back to its agent-level or name-based default). Max 64 chars, `[A-Za-z0-9_.-]` only."),
354044
- requireGenieSignature: exports_external.boolean().default(false).describe("When true, requests targeting this instance MUST carry a verified X-Genie-Signature. Bearer-only requests get 401. Default: false (additive rollout).")
354062
+ requireGenieSignature: exports_external.boolean().default(false).describe("When true, requests targeting this instance MUST carry a verified X-Genie-Signature. Bearer-only requests get 401. Default: false (additive rollout)."),
354063
+ allowFirstParty: exports_external.boolean().default(false).describe(`When true, this instance processes (does not drop) inbound messages whose sender matches another active instance owner. Lets an "assistant" instance reply to messages from the operator's own personal number. Default: false (loop-protection drop).`)
354045
354064
  });
354046
354065
  updateInstanceSchema = createInstanceSchema.partial().extend({
354047
354066
  agentId: exports_external.string().uuid().nullable().optional(),
@@ -354073,7 +354092,8 @@ var init_instances3 = __esm(() => {
354073
354092
  messageSplitDelayMinMs: exports_external.number().int().min(0).optional(),
354074
354093
  messageSplitDelayMaxMs: exports_external.number().int().min(0).optional(),
354075
354094
  twilioValidateSignature: exports_external.boolean().optional(),
354076
- requireGenieSignature: exports_external.boolean().optional()
354095
+ requireGenieSignature: exports_external.boolean().optional(),
354096
+ allowFirstParty: exports_external.boolean().optional()
354077
354097
  });
354078
354098
  DEFAULT_AGENT_REPLY_FILTER = {
354079
354099
  mode: "all",
@@ -361060,6 +361080,7 @@ async function setupMediaProcessor(eventBus, db2, services) {
361060
361080
  defaultLanguage,
361061
361081
  audioProvider,
361062
361082
  audioModel,
361083
+ geminiAudioModel,
361063
361084
  audioPrompt,
361064
361085
  imagePrompt,
361065
361086
  videoPrompt,
@@ -361071,6 +361092,7 @@ async function setupMediaProcessor(eventBus, db2, services) {
361071
361092
  services.settings.getString("media.default_language", "DEFAULT_LANGUAGE", "pt"),
361072
361093
  services.settings.getString("stt.provider", "STT_PROVIDER", "openai"),
361073
361094
  services.settings.getString("stt.openai.model", "OPENAI_STT_MODEL", "gpt-audio-mini"),
361095
+ services.settings.getString("stt.gemini.model", "GEMINI_STT_MODEL", GEMINI_AUDIO_MODEL),
361074
361096
  services.settings.getString("prompt.audio_transcription"),
361075
361097
  services.settings.getString("prompt.image_description"),
361076
361098
  services.settings.getString("prompt.video_description"),
@@ -361083,6 +361105,7 @@ async function setupMediaProcessor(eventBus, db2, services) {
361083
361105
  defaultLanguage,
361084
361106
  audioProvider: audioProvider ?? "openai",
361085
361107
  audioModel: audioModel ?? "gpt-audio-mini",
361108
+ geminiAudioModel: geminiAudioModel ?? GEMINI_AUDIO_MODEL,
361086
361109
  audioPrompt: audioPrompt ?? undefined
361087
361110
  });
361088
361111
  const mediaStorage2 = new MediaStorageService(db2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260704.1",
3
+ "version": "2.260704.2",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {