Commit Graph
6457 Commits
Author SHA1 Message Date
Tanmay Deep Sharma 852a219e0b feat(automations): narrow condition options for delayed rules instead of warning
When a delay is on, the condition attribute dropdown for conversation-level events
offers only Status, and attribute_changed is dropped from the operator list for any
delayed rule. Toggling the delay on resets conditions the delayed rule can't use.
This makes unsupported delayed rules unconstructable, so the inline restriction
warning (and its i18n strings) are removed.
2026-07-15 18:35:11 +05:30
Tanmay Deep Sharma be381b940d Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 18:12:30 +05:30
Tanmay Deep Sharma 3728d71176 feat(automations): make delayed execution a toggle that narrows the event list
Delayed execution is now a toggle chosen first (above Event). Turning it on limits
the Event dropdown to the events where a delayed rule is meaningful (Conversation
Updated for status waits, Message Created for awaiting-agent/reply-chase) and snaps
an unsupported event to a supported one, so users can't build a delayed rule on an
event the engine can't handle.
2026-07-15 18:12:21 +05:30
Tanmay Deep Sharma 9660b48c27 test(automations): sweep excludes rows for accounts with delayed automations disabled 2026-07-15 16:45:12 +05:30
Tanmay Deep Sharma a44645a7de Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 16:44:10 +05:30
Tanmay Deep Sharma 1e57cff70a fix(automations): exclude paused accounts from the sweep so they can't starve others
Now that a flag-off account's due rows stay pending (paused), order(:due_at).limit
would keep re-selecting that backlog every sweep, starving enabled accounts with
later due_at. Filter the sweep to accounts with delayed_automations enabled via a
for_enabled_accounts scope, so paused rows sit out of the limit until re-enabled.
2026-07-15 16:44:01 +05:30
Tanmay Deep Sharma 4c398b3c85 test(automations): account flag off pauses the row and resumes on re-enable 2026-07-15 16:19:31 +05:30
Tanmay Deep Sharma a521175422 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 16:19:03 +05:30
Tanmay Deep Sharma a921a38db9 fix(automations): pause (not skip) armed rows when the account flag is off
Marking a due row skipped/flag_disabled was terminal, so re-enabling the account
flag could never resume it (sweepable only sees pending/processing) — the delayed
action was lost, contradicting the banner that says rules resume when re-enabled.
Check the flag before claiming and return, leaving the row pending like the
instance kill switch does, so it fires once the feature is turned back on.
2026-07-15 16:18:55 +05:30
Tanmay Deep Sharma 7c5d6f8d72 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 15:59:23 +05:30
Tanmay Deep Sharma 7bb90d84cd fix(automations): hydrate delay controls from the edited rule on first open
The edit dialog's delay radios/value were synced once in open() from the automation
model, whose prop only settles a tick later — so the first edit click showed the
previous rule's delay (or immediate). Thread the clicked rule's execution_delay
directly into open() so the delay controls hydrate correctly on the first click.
2026-07-15 15:59:11 +05:30
Tanmay Deep Sharma 466763054c Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 15:31:04 +05:30
Tanmay Deep Sharma 21ea2ae08d revert(search): drop unnecessary reindex_for_search guard; trim automation_rule comments
The guard was dead code: should_index? is only true when searchkick is loaded
(both gated on advanced_search_allowed?), so #reindex is always defined when the
callback fires. It only masked a flaky develop-owned message_spec and contradicts
the fail-loudly-on-impossible-state guideline. Also trim verbose comments in
automation_rule.rb.
2026-07-15 15:30:46 +05:30
Tanmay Deep Sharma fd91ab84af test(automations): status episode keys use microsecond stamp and survive DB reload 2026-07-15 14:59:23 +05:30
Tanmay Deep Sharma e6ae59b8b2 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 14:58:49 +05:30
Tanmay Deep Sharma 35f14c7e02 fix(automations): use microsecond-integer stamp for status episode keys too
Same float64 imprecision as the awaiting-agent key: a status episode armed from an
in-memory status_changed_at (Time.current, nanosecond) could recompute to a slightly
different float once the worker reloads the DB-rounded value, skipping the row as
episode_moved. Use the shared microsecond_stamp helper so both sides agree.
2026-07-15 14:58:39 +05:30
Tanmay Deep Sharma e2c449a0c2 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 14:50:52 +05:30
Tanmay Deep Sharma 40b77ca001 fix(search): guard reindex_for_search when searchkick is not loaded
searchkick (which defines #reindex) is only mixed into Message at class-load time
when advanced_search_allowed? (enterprise + OPENSEARCH_URL) is true at boot. A spec
that stubs advanced_search_allowed? true without a loaded index makes should_index?
true and fires the reindex callback, raising NoMethodError. Guard on respond_to?
so it no-ops when no index is available (real deployments without search index
already have should_index? false, so behavior is unchanged).
2026-07-15 14:50:39 +05:30
Tanmay Deep Sharma 40e529fe4d test(automations): fix awaiting-agent key format and tighten job assertions
- Match the microsecond-integer (strftime %s%6N) awaiting-agent key format,
  fixing the CI failure from float epoch imprecision.
- Assert exactly one ProcessPendingExecutionJob is enqueued and the future row
  is not, so an early-fire regression is caught.
- Count the reply-chase follow-up as exactly one so a double-send regression fails.
- Stub the ActionService instance's perform (not new) to raise, exercising the
  job's error/retry path rather than only constructor failures.
2026-07-15 14:42:59 +05:30
Tanmay Deep Sharma d7b25d7db0 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 14:40:12 +05:30
Tanmay Deep Sharma e2cd372d2d fix(automations): use microsecond-integer stamp for awaiting-agent keys
Float epoch seconds carry ~16 significant digits, exceeding float64 precision,
and the arm path compares an in-memory created_at against the DB-stored
waiting_since. A float rounds differently on each side, so the armed key never
matched at fire time and the awaiting-agent automation was always skipped.
Use strftime('%s%6N') (integer microseconds) on both paths.
2026-07-15 14:40:05 +05:30
Tanmay Deep Sharma 17da6ee3ec test(automations): clone strips execution_delay when feature is disabled 2026-07-15 14:16:54 +05:30
Tanmay Deep Sharma b81f916002 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 14:16:20 +05:30
Tanmay Deep Sharma a1fdba2783 fix(automations): strip execution_delay when cloning a rule with the feature off
clone duplicates the rule via dup (including execution_delay) with no param for
ensure_execution_delay_allowed to inspect, so it could create new delayed rules
while the feature is disabled. Drop the delay on the clone unless the account has
delayed_automations enabled, matching create/update.
2026-07-15 14:16:10 +05:30
Tanmay Deep Sharma dea4d47602 test(automations): sub-second awaiting-agent keys and stale processing discard 2026-07-15 13:27:43 +05:30
Tanmay Deep Sharma 2728a90a2f Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 13:26:50 +05:30
Tanmay Deep Sharma d55c5660a3 fix(automations): sub-second awaiting-agent keys and discard stale processing rows on edit
- awaiting_agent episode keys use sub-second (.to_f) precision like status keys,
  so a reply then re-wait within the same second is a distinct episode and the
  original armed row no longer matches the later waiting period.
- discard_stale_pending_executions now deletes armed rows (pending and stale
  processing), since the sweep reclaims stale processing rows and would otherwise
  fire them against the edited rule definition.
2026-07-15 13:26:37 +05:30
Tanmay Deep Sharma 92bcd2f214 test(automations): cover event restriction, stale-edit discard, and awaiting-agent arm race 2026-07-15 13:08:29 +05:30
Tanmay Deep Sharma cc23867774 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 13:07:44 +05:30
Tanmay Deep Sharma 7a8903051d fix(automations): make awaiting-agent race fix asymmetric to preserve reply cancellation
The earlier waiting_since fallback in episode_key_for also matched at fire time,
so an agent reply (which clears waiting_since) no longer ended the episode and the
rule could fire anyway. Apply the created_at fallback only at arm time
(arm_episode_key_for); keep episode_key_for strict so a nil waiting_since at fire
time still means the agent replied and the episode is over.
2026-07-15 13:07:37 +05:30
Tanmay Deep Sharma d1d464dfb0 Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 13:00:22 +05:30
Tanmay Deep Sharma 1e6291616d fix(automations): harden delayed rules against races, stale edits, and unsupported configs
- episode_key_for falls back to the incoming message's created_at when
  waiting_since is still nil (it is written after MESSAGE_CREATED dispatches),
  so awaiting-agent episodes no longer arm as awaiting_agent:0 and get skipped
  as episode_moved on the first customer message after an agent reply.
- Editing a rule's trigger, conditions, or actions (not just the delay) now
  discards its armed pending rows so they can't fire against a definition they
  were never armed under; deleting rather than skipping frees the episode slot
  so the new definition re-arms on the next matching event.
- The rule form only offers a delay when the config supports it: the delayed
  option is disabled with an explanation for attribute_changed conditions and
  for non-status conditions on conversation events, and the backend's specific
  error is surfaced on save instead of a generic message.
2026-07-15 13:00:03 +05:30
Tanmay Deep Sharma 2e2fcdec47 test(automations): update feature flag bit-mapping spec for delayed_automations 2026-07-15 12:43:43 +05:30
Tanmay Deep Sharma 3436632a3b Merge branch 'feature/cw-7513' into feature/cw-7513-specs 2026-07-15 12:42:54 +05:30
Tanmay Deep Sharma 7a73a68753 fix(automations): cancel stale pending runs on delay edit; restrict conversation-level delayed rules to status conditions
- Editing a rule's execution_delay (removing or changing it) now cancels any
  pending executions armed under the old configuration instead of leaving
  them to fire on a stale schedule.
- conversation_created/updated/opened/resolved delayed rules key their episode
  on status_changed_at alone, so a delayed condition on any other attribute
  (assignee, team, priority, ...) could collapse distinct qualifying periods
  into one episode. Restricted to status conditions until episodes track
  per-attribute change times.
2026-07-15 12:36:58 +05:30
Tanmay Deep Sharma 55d68d930d Merge remote-tracking branch 'origin/develop' into feature/cw-7513
# Conflicts:
#	app/javascript/dashboard/featureFlags.js
#	config/features.yml
2026-07-15 12:31:43 +05:30
Tanmay Deep Sharma e328492463 refactor(automations): fold expiry check into skip_reason_for guard chain 2026-07-15 12:30:46 +05:30
Aakash BakhleandGitHub fd625981e9 fix(captain): keep custom tools available in Captain V2 (#15015)
Captain V2 assistants can now use every enabled custom tool from their
account through the main assistant. The change keeps existing custom
tool access when an assistant has no migrated scenarios, so switching
from V1 does not remove the capability without warning.

## How to reproduce

1. Create and enable an account custom tool.
2. Use an assistant with no custom instructions and no generated
scenarios.
3. Enable Captain V2 for the account.
4. Before this change, the main assistant receives only FAQ lookup and
handoff. After this change, it also receives the enabled account custom
tool.

## What changed

The main V2 assistant now loads enabled custom tools through its account
association. Scenario agents still load only the tools named in their
scenario instructions. The account custom tool limit keeps the added
tool count bounded.

Focused model coverage verifies enabled tools, disabled tools, account
isolation, FAQ lookup, and handoff. Existing V1 assistant, V2 scenario,
and V2 runner coverage passes. RuboCop passes.
2026-07-15 11:35:57 +05:30
49c442751d fix: require admin for dashboard app mutations (#14831)
## Summary

Restricts account-wide Dashboard App creation, updates, and deletion to
administrators while keeping read access available to authenticated
account users.

## Why

Dashboard Apps are account-level integrations displayed in conversation
views. Agents should be able to use them, but only administrators should
be able to change their configuration.

## What changed

- authorize Dashboard App actions through `DashboardAppPolicy`
- allow index and show access for authenticated account users
- restrict create, update, and destroy actions to administrators
- add request coverage for administrator and agent mutation behavior

## Validation

`bundle exec rspec
spec/controllers/api/v1/accounts/dashboard_apps_controller_spec.rb`

17 examples, 0 failures.

`bundle exec rubocop
app/controllers/api/v1/accounts/dashboard_apps_controller.rb
app/policies/dashboard_app_policy.rb`

2 files inspected, no offenses detected.



---------

Co-authored-by: Gaurav Singhal <gauravsinghal@Gauravs-Mac-mini.local>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-07-14 23:02:13 -07:00
Tanmay Deep Sharma ac9b055ada test(automations): specs for delayed automation rules 2026-07-15 10:57:41 +05:30
Tanmay Deep Sharma 82f28aa7fb chore(automations): move specs to a follow-up branch 2026-07-15 10:57:23 +05:30
Tanmay Deep Sharma 5c0cbcbaaa chore(automations): remove design docs from branch 2026-07-15 10:56:07 +05:30
Tanmay Deep Sharma 5d5fa0c21a refactor(automations): job-side claim for delayed executions, remove rubocop disables 2026-07-15 10:50:54 +05:30
13db36609d fix: hide agent bot access tokens from agents (#14830)
## Summary

Keeps Agent Bot list and show access available to agents while
restricting account bot access tokens to administrators.

## Why

Agents need Agent Bot metadata for existing product workflows, but the
bot access token can be replayed against bot-authorized APIs and should
not be exposed to them.

## What changed

- serialize `access_token` only for administrators
- verify agents can read Agent Bot metadata without receiving the token
- verify administrators still receive the token from index and show
responses

## Validation

`bundle exec rspec
spec/controllers/api/v1/accounts/agent_bots_controller_spec.rb`

27 examples, 0 failures.

Related follow-up:
[CW-7595](https://linear.app/chatwoot/issue/CW-7595/standardize-one-time-credential-disclosure-across-chatwoot-apis)



---------

Co-authored-by: Gaurav Singhal <gauravsinghal@Gauravs-Mac-mini.local>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-07-14 20:00:39 -07:00
Muhsin KelothandGitHub 8b4f3e226e revert: "fix(meta): disable Instagram replies on Cloud during restriction" (#15020)
Reverts chatwoot/chatwoot#15005
2026-07-14 14:59:41 -07:00
Aakash BakhleandGitHub 2ac55c8728 fix(captain): honor mandatory handoff guidelines (#15003)
Ensures Captain follows explicit mandatory-transfer rules from active
Response Guidelines and Guardrails instead of allowing the generic
consent-first fallback to override those rules.

## What changed

- Made explicit transfer requirements take precedence over generic
consent-first handoff defaults only when their condition matches.
- Added explicit Response Guideline and Guardrail transfer rules to the
human-handoff protocol.
- Added focused prompt regression coverage.

## How to reproduce

Configure a Response Guideline or Guardrail that requires immediate
transfer for a specific condition, then send a request matching that
condition. Captain should invoke the human-handoff path without asking
the user to consent again. Unmatched requests continue to use the
existing consent-first fallback.

The assistant prompt renderer, agent prompt context, and focused
regression specs pass locally.
2026-07-14 19:38:43 +05:30
9328f8739c fix: clear whatsapp webhook override when manual cloud inbox is deleted (#15010)
Deleting a manually-configured WhatsApp Cloud inbox left its
phone-number-level webhook override still pointing at Chatwoot on Meta's
side. The number kept routing inbound events to us after the inbox was
gone, which blocked the customer's own app — subscribed separately on
the same WABA — from receiving messages, since the phone-level override
takes priority over the app-level subscription. Deleting the inbox now
releases the override, as it already did for embedded-signup inboxes.

## What changed

The setup and teardown paths gated on opposite halves of the same
condition. `Channel::Whatsapp#should_auto_setup_webhooks?` sets the
override for `whatsapp_cloud` inboxes where `source !=
'embedded_signup'` (i.e. manual ones), while
`Whatsapp::WebhookTeardownService#should_teardown_webhook?` only cleared
it when `source == 'embedded_signup'`. The two sets are disjoint, so
manual inboxes were exactly the ones that set an override on create and
never cleared it on destroy. Embedded-signup inboxes were unaffected
because `EmbeddedSignupService` calls `setup_webhooks` explicitly.

Dropping the `source` check from the teardown guard is the whole fix.
Manual `whatsapp_cloud` channels can't persist without `api_key`,
`phone_number_id` and `business_account_id` (`validate_provider_config`
verifies all three against Meta), so the remaining presence guards and
both API calls have everything they need. The WABA-level `DELETE
/subscribed_apps` now also fires for manual inboxes when the last one on
a WABA is removed, which is symmetric with manual setup subscribing the
app in the first place; the token only unsubscribes the app it belongs
to, so a customer's separate app subscription is untouched.

This fixes the leak going forward. Numbers already stranded still need
the override cleared with the customer's own token, since we no longer
hold their `api_key` once the inbox is deleted.

## How to reproduce

1. Create a WhatsApp Cloud inbox using manual API keys (not embedded
signup).
2. Confirm the override is set: `GET
/v22.0/{phone_number_id}?fields=webhook_configuration` shows
`phone_number` pointing at your Chatwoot install.
3. Delete the inbox.
4. Before this change, the override still points at Chatwoot. After it,
`webhook_configuration` no longer carries the phone-level override and
events fall back to the WABA/app-level subscription.

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-07-14 15:05:27 +05:30
3e03f8da1e chore(whatsapp): log warning when Cloud API template sync fails (#15004)
WhatsApp Cloud API template sync currently fails silently — if the Graph
API call errors (expired token, rate limit, permission issue), the
channel simply keeps its stale templates with no trace in the logs. This
adds a warning log when the template fetch fails, so failed syncs are
visible and debuggable.

## What changed

- `Whatsapp::Providers::WhatsappCloudService#fetch_whatsapp_templates`
now logs a warning with the account id, inbox id, HTTP status code, and
Meta's error message when the response is not successful.
- The inbox id uses safe navigation since sync also runs from the
channel's `after_create` callback, before the inbox record exists.
- The request URL is intentionally not logged, as it contains the access
token as a query param.

## How to reproduce

1. Set up a WhatsApp Cloud inbox with an invalid/expired `api_key`.
2. Trigger a template sync (Inbox settings → sync templates, or wait for
the scheduler).
3. Previously nothing was logged; now a `[WHATSAPP] Template sync failed
for account ... inbox ...` warning appears in the Rails logs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-07-14 13:35:10 +04:00
280756b483 fix(meta): disable Instagram replies on Cloud during restriction (#15005)
Agents can no longer send replies in Instagram conversations on Chatwoot
Cloud while the temporary Meta platform restriction is active. The reply
box locks into Private Note mode — the same behavior as an expired
24-hour reply window — so teams can still collaborate internally, with
the existing amber restriction banner above the conversation explaining
why. Self-hosted installations are unaffected.

Follow-up to #14974.


## How to test

1. On a Chatwoot Cloud environment (`isOnChatwootCloud` true), open any
Instagram conversation.
2. The composer should be locked to Private Note mode: the Reply/Private
Note toggle is disabled, and sending creates a private note — even for
conversations within the 24-hour reply window.
3. Switching between conversations should keep the composer in Private
Note mode for Instagram conversations.
4. On a self-hosted environment, Instagram conversations should behave
as before (reply allowed within the messaging window).

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
2026-07-14 13:30:19 +04:00
Aakash BakhleandGitHub 102f19fe41 feat(captain): add FAQ suggestion data model (1/3) (#14977)
Resolved conversations need a separate suggestion layer so repeated FAQ
signals can be grouped without creating untrusted knowledge entries.
This PR adds the persistence foundation only; it introduces no
user-facing behavior by itself.

## Closes

-
[CW-7495](https://linear.app/chatwoot/issue/CW-7495/backend-llm-changes-to-make-conversation-faqs-as-signalssuggestions)
(stacked PR 1/3; the issue is complete after the full stack lands)

## What changed

- Added `captain_faq_suggestions` with question, answer, embedding,
source count, and review status.
- Added `captain_faq_observations` to retain conversation-level signals.
- Added Captain assistant, account, and conversation associations.
- Added vector and lookup indexes for semantic grouping.

## How to test

This layer has no standalone UI behavior. Apply the migration and
confirm Captain assistants can persist open FAQ suggestions with
attached conversation observations.
2026-07-14 14:31:08 +05:30