flow_chat 0.10.0 → 0.10.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +4 -1
- data/CHANGELOG.md +23 -0
- data/flow_chat.gemspec +22 -2
- data/gemfiles/rails_main.gemfile +18 -0
- data/lib/flow_chat/async_job.rb +10 -0
- data/lib/flow_chat/gateway_async_support.rb +19 -1
- data/lib/flow_chat/meta/messaging_gateway.rb +15 -12
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +14 -9
- data/lib/tasks/release.rake +11 -1
- metadata +10 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6ae06aa071a9ac6f07a9b2555e4e35dcaf98512962625b45426865484d989f7c
|
|
4
|
+
data.tar.gz: 583ef52a86cd6462dd3d0bb5e6b1cf76267ea3bbfcd0d9b33b2d7ceffa065e9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 22a819e0db8e6784933ff311b48aa3d9f62eb38aa456df985ee2b4d5c7d8fb683bc866efebe3643294b3ef49c1f5b041b1c6bd77a514e2f9dd63d913cc4e8a3a
|
|
7
|
+
data.tar.gz: bc2057d4d6aed38010c635fa56d91f756d5ecbbfc527d5b21ff2c4d04457b5672367321d00499d8e7a37c374352891ce15c14210c94738ef52114ff2676fdb1d
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -28,6 +28,35 @@ jobs:
|
|
|
28
28
|
- name: Build gem
|
|
29
29
|
run: bundle exec gem build *.gemspec
|
|
30
30
|
|
|
31
|
+
# Watches unreleased Rails, because flow_chat's activesupport and actionpack
|
|
32
|
+
# dependencies are deliberately unbounded above (see flow_chat.gemspec).
|
|
33
|
+
#
|
|
34
|
+
# Allowed to fail. A red run here means Rails main changed something this gem
|
|
35
|
+
# relies on, and is a prompt to look rather than a broken build - the point is
|
|
36
|
+
# to find out while the change is still unreleased, instead of from a bug
|
|
37
|
+
# report the week the major ships.
|
|
38
|
+
rails-main:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
continue-on-error: true
|
|
41
|
+
env:
|
|
42
|
+
BUNDLE_GEMFILE: gemfiles/rails_main.gemfile
|
|
43
|
+
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
|
|
47
|
+
# Newest stable, deliberately ahead of the matrix above. Rails main already
|
|
48
|
+
# requires Ruby >= 3.3 (via ractor-dispatch) and that floor keeps rising, so
|
|
49
|
+
# pinning this job to the minimum would fail it for a Ruby version rather
|
|
50
|
+
# than for the API change it exists to catch.
|
|
51
|
+
- name: Set up Ruby
|
|
52
|
+
uses: ruby/setup-ruby@v1
|
|
53
|
+
with:
|
|
54
|
+
ruby-version: '3.4'
|
|
55
|
+
bundler-cache: true
|
|
56
|
+
|
|
57
|
+
- name: Run tests against rails main
|
|
58
|
+
run: bundle exec rake test
|
|
59
|
+
|
|
31
60
|
lint:
|
|
32
61
|
runs-on: ubuntu-latest
|
|
33
62
|
steps:
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.10.1] - 2026-08-17
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Publish side events when no foreground pass ran
|
|
10
|
+
|
|
11
|
+
### Documentation
|
|
12
|
+
|
|
13
|
+
- Record #9 in the 0.10.0 release notes
|
|
14
|
+
|
|
15
|
+
### Miscellaneous Tasks
|
|
16
|
+
|
|
17
|
+
- State the Ruby floor, and watch unreleased Rails instead of capping ([#10](https://github.com/radioactive-labs/flow_chat/issues/10))
|
|
18
|
+
- Prepend the changelog instead of regenerating it
|
|
19
|
+
|
|
5
20
|
## [0.10.0] - 2026-08-17
|
|
6
21
|
|
|
22
|
+
> **Note.** [#9](https://github.com/radioactive-labs/flow_chat/pull/9) was squash-merged with a non-conventional title, so git-cliff did not pick it up when this section was generated. It is the largest change in the release and is listed by hand below. Releases now prepend rather than regenerate, so this survives.
|
|
23
|
+
|
|
24
|
+
### Behaviour changes
|
|
25
|
+
|
|
26
|
+
- **Intercom resolves numbers only.** The renderer already prints a number beside every option and asks for one; matching typed labels as well is what allowed two options reading the same to collapse onto a single mapping entry. Typing the words now falls through to the flow's own validation.
|
|
27
|
+
- **HTTP matches replies exactly**, with no case or whitespace folding. A web client echoes back the string it was handed, so nothing drifts in transit. A client that lowercases its reply will stop resolving.
|
|
28
|
+
|
|
7
29
|
### Bug Fixes
|
|
8
30
|
|
|
9
31
|
- Correct runtime-breaking session and API misuse
|
|
@@ -18,6 +40,7 @@ All notable changes to this project will be documented in this file.
|
|
|
18
40
|
|
|
19
41
|
### Features
|
|
20
42
|
|
|
43
|
+
- Add Facebook Messenger and Instagram DM gateways, unify choice resolution, and fix delivery instrumentation ([#9](https://github.com/radioactive-labs/flow_chat/pull/9))
|
|
21
44
|
- Map choices on Intercom and HTTP ([#4](https://github.com/radioactive-labs/flow_chat/issues/4))
|
|
22
45
|
- Let a flow declare that it consumed the turn ([#6](https://github.com/radioactive-labs/flow_chat/issues/6))
|
|
23
46
|
- Report a reply the platform would not take ([#7](https://github.com/radioactive-labs/flow_chat/issues/7))
|
data/flow_chat.gemspec
CHANGED
|
@@ -14,7 +14,11 @@ Gem::Specification.new do |spec|
|
|
|
14
14
|
DESC
|
|
15
15
|
spec.homepage = "https://github.com/radioactive-labs/flow_chat"
|
|
16
16
|
spec.license = "MIT"
|
|
17
|
-
|
|
17
|
+
# Matches the versions CI actually exercises. The previous ">= 2.3.0" was
|
|
18
|
+
# three Rubies out of date and promised a floor nothing here could honour:
|
|
19
|
+
# activesupport 8 requires Ruby 3.2, so a 2.x user hit an unexplained
|
|
20
|
+
# resolution failure rather than a clear statement of what this gem needs.
|
|
21
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 3.0")
|
|
18
22
|
|
|
19
23
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
|
20
24
|
spec.metadata["rubygems_mfa_required"] = "true"
|
|
@@ -32,9 +36,25 @@ Gem::Specification.new do |spec|
|
|
|
32
36
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
33
37
|
spec.require_paths = ["lib"]
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
# Deliberately unbounded above, and RubyGems' warning about that is
|
|
40
|
+
# overridden knowingly rather than overlooked.
|
|
41
|
+
#
|
|
42
|
+
# A "< N" cap does not protect this gem from the next major - it decides
|
|
43
|
+
# where the breakage surfaces. Capping blocks every downstream application on
|
|
44
|
+
# the day that major ships, until this gem cuts a release, which is a cost
|
|
45
|
+
# paid by everyone for a break that may never come.
|
|
46
|
+
#
|
|
47
|
+
# Leaving it open moves the risk from install time to runtime, and the CI job
|
|
48
|
+
# against rails main is what pays for that: an incompatible change is found
|
|
49
|
+
# while it is still unreleased, rather than in a bug report the week it lands.
|
|
50
|
+
# If that job is ever removed, these bounds should be revisited.
|
|
51
|
+
#
|
|
52
|
+
# No floor is needed beyond ">= 6" either. Bundler resolves an activesupport
|
|
53
|
+
# that suits the running Ruby, so a Ruby 3.0 application lands on Rails 7.x
|
|
54
|
+
# without this gem having to say so.
|
|
36
55
|
spec.add_dependency "activesupport", ">= 6"
|
|
37
56
|
spec.add_dependency "actionpack", ">= 6"
|
|
57
|
+
spec.add_dependency "zeitwerk"
|
|
38
58
|
spec.add_dependency "phonelib"
|
|
39
59
|
spec.add_dependency "ibsciss-middleware", "~> 0.4.2"
|
|
40
60
|
spec.add_dependency "intercom", "~> 4.2"
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Resolves against unreleased Rails, for the CI job that watches for breaking
|
|
2
|
+
# changes before they ship.
|
|
3
|
+
#
|
|
4
|
+
# This exists because flow_chat's activesupport and actionpack dependencies are
|
|
5
|
+
# deliberately unbounded above (see flow_chat.gemspec). Without an upper bound,
|
|
6
|
+
# an incompatible major would otherwise be discovered by an application that
|
|
7
|
+
# upgraded, rather than here. The job is allowed to fail: a red run is a signal
|
|
8
|
+
# to look, not a broken build.
|
|
9
|
+
#
|
|
10
|
+
# The root Gemfile is evaluated rather than restated, so this file carries the
|
|
11
|
+
# development dependencies too - rake, minitest, standard and the rest. Listing
|
|
12
|
+
# only the gemspec here left the suite unable to load its own Rakefile, which
|
|
13
|
+
# failed the job for a reason that had nothing to do with Rails.
|
|
14
|
+
eval_gemfile File.expand_path("../Gemfile", __dir__)
|
|
15
|
+
|
|
16
|
+
# Overrides the activesupport/actionpack the gemspec resolved, which is the
|
|
17
|
+
# whole point of this file.
|
|
18
|
+
gem "rails", github: "rails/rails", branch: "main"
|
data/lib/flow_chat/async_job.rb
CHANGED
|
@@ -70,9 +70,19 @@ module FlowChat
|
|
|
70
70
|
def initialize(request_data)
|
|
71
71
|
FlowChat.logger.debug { "BackgroundController: Initializing with request data" }
|
|
72
72
|
@request = BackgroundRequest.new(request_data)
|
|
73
|
+
@side_events_published = request_data[:side_events_published] || false
|
|
73
74
|
@response = nil
|
|
74
75
|
end
|
|
75
76
|
|
|
77
|
+
# Whether the pass that enqueued this job already announced the delivery's
|
|
78
|
+
# side events. Only the gem sets it, when it enqueues a job of its own.
|
|
79
|
+
# An application that builds a request context by hand and enqueues
|
|
80
|
+
# directly has no earlier pass behind it, so the answer is false and the
|
|
81
|
+
# job is the one that announces them.
|
|
82
|
+
def side_events_published?
|
|
83
|
+
@side_events_published
|
|
84
|
+
end
|
|
85
|
+
|
|
76
86
|
# Delegate params to request (mimics Rails controller behavior)
|
|
77
87
|
def params
|
|
78
88
|
request.params
|
|
@@ -17,6 +17,18 @@ module FlowChat
|
|
|
17
17
|
@controller.is_a?(::FlowChat::BackgroundController)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
# Whether an earlier pass already announced this delivery's side events,
|
|
21
|
+
# which is the case only for a job the gem enqueued after publishing them
|
|
22
|
+
# itself. Being in the background is not enough on its own: an application
|
|
23
|
+
# may build a request context and enqueue a job with no foreground pass
|
|
24
|
+
# ahead of it, and then this pass is the first to see the delivery.
|
|
25
|
+
#
|
|
26
|
+
# in_background? short-circuits because a foreground controller is a Rails
|
|
27
|
+
# controller, which knows nothing about side events.
|
|
28
|
+
def side_events_already_published?
|
|
29
|
+
in_background? && @controller.side_events_published?
|
|
30
|
+
end
|
|
31
|
+
|
|
20
32
|
# Check if async processing should be used
|
|
21
33
|
# Returns true if:
|
|
22
34
|
# - Not already in background mode
|
|
@@ -40,6 +52,11 @@ module FlowChat
|
|
|
40
52
|
FlowChat.logger.info { "#{self.class.name}: Async enabled - enqueuing background job" }
|
|
41
53
|
|
|
42
54
|
# Serialize request data for BackgroundController
|
|
55
|
+
#
|
|
56
|
+
# side_events_published records that this pass has already announced the
|
|
57
|
+
# delivery's side events, so the job it is about to enqueue does not
|
|
58
|
+
# announce them a second time. It rides in the request context rather
|
|
59
|
+
# than in params, where a platform's own webhook fields live.
|
|
43
60
|
request_data = {
|
|
44
61
|
params: @controller.request.params.to_h,
|
|
45
62
|
method: @controller.request.method,
|
|
@@ -47,7 +64,8 @@ module FlowChat
|
|
|
47
64
|
host: extract_host(@controller.request),
|
|
48
65
|
path: extract_path(@controller.request),
|
|
49
66
|
body: extract_body_for_background(@controller.request),
|
|
50
|
-
remote_ip: extract_remote_ip(@controller.request)
|
|
67
|
+
remote_ip: extract_remote_ip(@controller.request),
|
|
68
|
+
side_events_published: true
|
|
51
69
|
}
|
|
52
70
|
|
|
53
71
|
# Enqueue user's job with request context and job params
|
|
@@ -111,20 +111,23 @@ module FlowChat
|
|
|
111
111
|
flow_ran = false
|
|
112
112
|
|
|
113
113
|
entries.each do |entry|
|
|
114
|
-
# Published
|
|
115
|
-
#
|
|
114
|
+
# Published by whichever pass is first to see this delivery, and
|
|
115
|
+
# only once.
|
|
116
116
|
#
|
|
117
|
-
# With async enabled the
|
|
118
|
-
#
|
|
119
|
-
# and standby event twice
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
# gets to it, and survives a
|
|
117
|
+
# With async enabled the gem publishes here, enqueues, and the job
|
|
118
|
+
# re-enters this method on the same body, so publishing in both
|
|
119
|
+
# announced every receipt, echo and standby event twice. The job the
|
|
120
|
+
# gem enqueues therefore carries word that they are already out, and
|
|
121
|
+
# skips them. The request is the better publisher of the two: it
|
|
122
|
+
# already holds the delivery, so a receipt is announced when it
|
|
123
|
+
# arrives rather than whenever the queue gets to it, and survives a
|
|
124
|
+
# job that is never picked up.
|
|
124
125
|
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
|
|
126
|
+
# A background pass with nothing behind it publishes. An application
|
|
127
|
+
# is free to build its own request context and enqueue a job itself,
|
|
128
|
+
# which is what fanning one shared webhook out to several accounts
|
|
129
|
+
# takes, and no foreground pass ever runs for those.
|
|
130
|
+
publish_side_events(entry) unless side_events_already_published?
|
|
128
131
|
|
|
129
132
|
events = entry["messaging"]
|
|
130
133
|
next unless events.is_a?(Array)
|
data/lib/flow_chat/version.rb
CHANGED
|
@@ -147,13 +147,18 @@ module FlowChat
|
|
|
147
147
|
# slot is claimed, or a status arriving ahead of a message in the
|
|
148
148
|
# same delivery would spend the slot and drop the message.
|
|
149
149
|
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
|
|
150
|
+
# Announced by whichever pass is first to see the delivery, and
|
|
151
|
+
# only once. With async enabled the gem publishes here, enqueues,
|
|
152
|
+
# and the job re-enters this method on the same body, so
|
|
153
|
+
# publishing in both double-counted every receipt. The job the
|
|
154
|
+
# gem enqueues carries word that they are already out and skips
|
|
155
|
+
# them, which leaves the request publishing: it already holds
|
|
156
|
+
# the delivery, so a receipt is announced when it arrives rather
|
|
157
|
+
# than when the queue reaches it, and survives a job that is
|
|
158
|
+
# never picked up. A background pass with no foreground pass
|
|
159
|
+
# behind it, as an application enqueuing its own jobs has,
|
|
160
|
+
# publishes them itself.
|
|
161
|
+
handle_statuses(value) if value["statuses"].present? && !side_events_already_published?
|
|
157
162
|
|
|
158
163
|
next if value["messages"].blank?
|
|
159
164
|
|
|
@@ -171,13 +176,13 @@ module FlowChat
|
|
|
171
176
|
when "statuses"
|
|
172
177
|
# Only reachable for a payload built without a field name, which
|
|
173
178
|
# our own fixtures do and Meta does not.
|
|
174
|
-
handle_statuses(value) unless
|
|
179
|
+
handle_statuses(value) unless side_events_already_published?
|
|
175
180
|
else
|
|
176
181
|
# Anything that is not a message or its delivery. Coexistence
|
|
177
182
|
# echoes, contact syncs, imported history, account bans, template
|
|
178
183
|
# approvals: all of it is the application's domain, so it is
|
|
179
184
|
# published rather than interpreted here.
|
|
180
|
-
handle_unmodelled_field(change["field"], value, entry["id"]) unless
|
|
185
|
+
handle_unmodelled_field(change["field"], value, entry["id"]) unless side_events_already_published?
|
|
181
186
|
end
|
|
182
187
|
end
|
|
183
188
|
end
|
data/lib/tasks/release.rake
CHANGED
|
@@ -94,8 +94,18 @@ namespace :release do
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
# Changelog — same config CI uses for release notes, so they agree.
|
|
97
|
+
#
|
|
98
|
+
# Prepends the new section rather than regenerating the file. `-o` rewrote
|
|
99
|
+
# CHANGELOG.md from the commit history every time, so anything hand-written
|
|
100
|
+
# was silently discarded at the next release. That is not hypothetical: #9
|
|
101
|
+
# was squash-merged with a non-conventional title, git-cliff could not
|
|
102
|
+
# categorise it, and the largest change in v0.10.0 had to be written in by
|
|
103
|
+
# hand — which `-o` would have erased.
|
|
104
|
+
#
|
|
105
|
+
# --unreleased limits the run to commits since the last tag, so only the
|
|
106
|
+
# section being cut is generated and everything below it is left alone.
|
|
97
107
|
abort "git-cliff not found. Install with: brew install git-cliff" unless git_cliff?
|
|
98
|
-
system("git-cliff", "--config", RELEASE_CLIFF_CONFIG, "--tag", "v#{version}", "
|
|
108
|
+
system("git-cliff", "--config", RELEASE_CLIFF_CONFIG, "--tag", "v#{version}", "--unreleased", "--prepend", "CHANGELOG.md") ||
|
|
99
109
|
abort("Changelog generation failed")
|
|
100
110
|
puts "✓ CHANGELOG.md"
|
|
101
111
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: flow_chat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stefan Froelich
|
|
@@ -11,21 +11,21 @@ cert_chain: []
|
|
|
11
11
|
date: 2026-08-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name:
|
|
14
|
+
name: activesupport
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
17
|
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
19
|
+
version: '6'
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
24
|
- - ">="
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
26
|
+
version: '6'
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
28
|
+
name: actionpack
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
@@ -39,19 +39,19 @@ dependencies:
|
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: '6'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
42
|
+
name: zeitwerk
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
45
|
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '0'
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - ">="
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '0'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: phonelib
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- examples/whatsapp_controller.rb
|
|
184
184
|
- examples/whatsapp_media_examples.rb
|
|
185
185
|
- flow_chat.gemspec
|
|
186
|
+
- gemfiles/rails_main.gemfile
|
|
186
187
|
- lib/flow_chat.rb
|
|
187
188
|
- lib/flow_chat/app.rb
|
|
188
189
|
- lib/flow_chat/async_job.rb
|
|
@@ -285,7 +286,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
285
286
|
requirements:
|
|
286
287
|
- - ">="
|
|
287
288
|
- !ruby/object:Gem::Version
|
|
288
|
-
version:
|
|
289
|
+
version: '3.0'
|
|
289
290
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
290
291
|
requirements:
|
|
291
292
|
- - ">="
|