flow_chat 0.8.1 → 0.9.0
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/.cliff.toml +74 -0
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/release.yml +56 -0
- data/.standard.yml +4 -0
- data/CHANGELOG.md +22 -0
- data/CLAUDE.md +327 -0
- data/CONTRIBUTING.md +134 -0
- data/Gemfile +1 -0
- data/README.md +290 -105
- data/Rakefile +5 -1
- data/SECURITY.md +42 -349
- data/docs/architecture.md +510 -0
- data/docs/async-background-processing.md +298 -0
- data/docs/configuration.md +556 -226
- data/docs/factory-pattern.md +355 -0
- data/docs/gateway-context-variables.md +171 -0
- data/docs/gateway-development.md +723 -0
- data/docs/getting-started.md +429 -0
- data/docs/instrumentation.md +264 -153
- data/docs/platforms/telegram.md +1013 -0
- data/docs/platforms/ussd.md +693 -0
- data/docs/platforms/whatsapp.md +1395 -0
- data/docs/testing.md +243 -365
- data/examples/custom_session_id_example.rb +119 -0
- data/examples/http_controller.rb +154 -0
- data/examples/intercom_configuration_example.rb +118 -0
- data/examples/intercom_controller.rb +194 -0
- data/examples/multi_tenant_whatsapp_controller.rb +4 -4
- data/examples/simulator_controller.rb +21 -1
- data/examples/ussd_controller.rb +10 -10
- data/examples/whatsapp_controller.rb +2 -2
- data/flow_chat.gemspec +4 -0
- data/lib/flow_chat/{base_app.rb → app.rb} +28 -9
- data/lib/flow_chat/async_job.rb +176 -0
- data/lib/flow_chat/config.rb +23 -28
- data/lib/flow_chat/{base_executor.rb → executor.rb} +7 -12
- data/lib/flow_chat/factory.rb +94 -0
- data/lib/flow_chat/gateway_async_support.rb +106 -0
- data/lib/flow_chat/generic_async_job.rb +30 -0
- data/lib/flow_chat/http/gateway/simple.rb +125 -0
- data/lib/flow_chat/http/renderer.rb +41 -0
- data/lib/flow_chat/instrumentation/setup.rb +1 -1
- data/lib/flow_chat/instrumentation.rb +25 -0
- data/lib/flow_chat/intercom/client.rb +155 -0
- data/lib/flow_chat/intercom/configuration.rb +149 -0
- data/lib/flow_chat/intercom/gateway/intercom_api.rb +396 -0
- data/lib/flow_chat/intercom/renderer.rb +71 -0
- data/lib/flow_chat/phone_number_util.rb +49 -0
- data/lib/flow_chat/processor.rb +188 -0
- data/lib/flow_chat/renderers/markdown_support.rb +58 -0
- data/lib/flow_chat/session/cache_session_store.rb +1 -17
- data/lib/flow_chat/session/middleware.rb +43 -26
- data/lib/flow_chat/simulator/controller.rb +17 -5
- data/lib/flow_chat/simulator/views/simulator.html.erb +220 -8
- data/lib/flow_chat/telegram/client.rb +240 -0
- data/lib/flow_chat/telegram/configuration.rb +118 -0
- data/lib/flow_chat/telegram/gateway/bot_api.rb +300 -0
- data/lib/flow_chat/telegram/middleware/choice_mapper.rb +35 -0
- data/lib/flow_chat/telegram/renderer.rb +125 -0
- data/lib/flow_chat/telegram.rb +7 -0
- data/lib/flow_chat/ussd/gateway/nalo.rb +27 -11
- data/lib/flow_chat/ussd/middleware/pagination.rb +9 -5
- data/lib/flow_chat/ussd/renderer.rb +1 -1
- data/lib/flow_chat/version.rb +1 -1
- data/lib/flow_chat/whatsapp/client.rb +144 -13
- data/lib/flow_chat/whatsapp/configuration.rb +1 -1
- data/lib/flow_chat/whatsapp/gateway/cloud_api.rb +144 -106
- data/lib/flow_chat/whatsapp/id_generator.rb +124 -0
- data/lib/flow_chat/whatsapp/middleware/choice_mapper.rb +147 -0
- data/lib/flow_chat/whatsapp/renderer.rb +145 -11
- data/lib/flow_chat.rb +11 -1
- data/lib/tasks/release.rake +165 -0
- metadata +88 -21
- data/docs/flows.md +0 -320
- data/docs/images/simulator.png +0 -0
- data/docs/media.md +0 -153
- data/docs/sessions.md +0 -433
- data/docs/ussd-setup.md +0 -322
- data/docs/whatsapp-setup.md +0 -162
- data/examples/whatsapp_message_job.rb +0 -113
- data/lib/flow_chat/base_processor.rb +0 -145
- data/lib/flow_chat/session/rails_session_store.rb +0 -68
- data/lib/flow_chat/ussd/app.rb +0 -6
- data/lib/flow_chat/ussd/gateway/nsano.rb +0 -98
- data/lib/flow_chat/ussd/middleware/executor.rb +0 -24
- data/lib/flow_chat/ussd/processor.rb +0 -39
- data/lib/flow_chat/whatsapp/app.rb +0 -29
- data/lib/flow_chat/whatsapp/middleware/executor.rb +0 -24
- data/lib/flow_chat/whatsapp/processor.rb +0 -32
- data/lib/flow_chat/whatsapp/send_job_support.rb +0 -79
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8cfb138af20136fca40bdc0d7b054e8144ab92e2089c6c446e8c06f5104600f9
|
|
4
|
+
data.tar.gz: c4ae6902cffe3bcada4c3efe7b72f544fa9ec246570fa72df1b27c977c4f52db
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f1e719da60370142efbd2b1a44cbb24236e67291340f9015ddd38411b8180cc2f3216d5e56a50464e23a4657d5184ec80ae85225b86e20f1e6673fcc607c1941
|
|
7
|
+
data.tar.gz: ac3fda919d3fe2bfc939be1454b3e4a4edaf8d4c8d18869244c1492a0bc593d186e0ed5fae626ef035a6905e83433d0197560ceac6bc5bce2a87ffcaa53a3ddd
|
data/.cliff.toml
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# git-cliff configuration file
|
|
2
|
+
# https://git-cliff.org/docs/configuration
|
|
3
|
+
|
|
4
|
+
[changelog]
|
|
5
|
+
# changelog header
|
|
6
|
+
header = """
|
|
7
|
+
# Changelog\n
|
|
8
|
+
All notable changes to this project will be documented in this file.\n
|
|
9
|
+
"""
|
|
10
|
+
# template for the changelog body
|
|
11
|
+
body = """
|
|
12
|
+
{% if version %}\
|
|
13
|
+
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
14
|
+
{% else %}\
|
|
15
|
+
## [Unreleased]
|
|
16
|
+
{% endif %}\
|
|
17
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
18
|
+
### {{ group | upper_first }}
|
|
19
|
+
{% for commit in commits %}
|
|
20
|
+
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
|
|
21
|
+
{% endfor %}
|
|
22
|
+
{% endfor %}\n
|
|
23
|
+
"""
|
|
24
|
+
# remove the leading and trailing whitespace from the template
|
|
25
|
+
trim = true
|
|
26
|
+
# changelog footer
|
|
27
|
+
footer = """
|
|
28
|
+
<!-- generated by git-cliff -->
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
[git]
|
|
32
|
+
# parse the commits based on https://www.conventionalcommits.org
|
|
33
|
+
conventional_commits = true
|
|
34
|
+
# filter out the commits that are not conventional
|
|
35
|
+
filter_unconventional = true
|
|
36
|
+
# process each line of a commit as an individual commit
|
|
37
|
+
split_commits = false
|
|
38
|
+
# regex for preprocessing the commit messages
|
|
39
|
+
commit_preprocessors = [
|
|
40
|
+
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/radioactive-labs/flow_chat/issues/${2}))" },
|
|
41
|
+
]
|
|
42
|
+
# regex for parsing and grouping commits
|
|
43
|
+
commit_parsers = [
|
|
44
|
+
{ message = "^feat", group = "Features" },
|
|
45
|
+
{ message = "^fix", group = "Bug Fixes" },
|
|
46
|
+
{ message = "^doc", group = "Documentation" },
|
|
47
|
+
{ message = "^perf", group = "Performance" },
|
|
48
|
+
{ message = "^refactor", group = "Refactoring" },
|
|
49
|
+
{ message = "^style", group = "Styling" },
|
|
50
|
+
{ message = "^test", group = "Testing" },
|
|
51
|
+
{ message = "^chore\\(release\\): prepare for", skip = true },
|
|
52
|
+
{ message = "^chore", group = "Miscellaneous Tasks" },
|
|
53
|
+
{ body = ".*security", group = "Security" },
|
|
54
|
+
]
|
|
55
|
+
# protect breaking changes from being skipped due to matching a skipping commit_parser
|
|
56
|
+
protect_breaking_commits = false
|
|
57
|
+
# filter out the commits that are not matched by commit parsers
|
|
58
|
+
filter_commits = false
|
|
59
|
+
# glob pattern for matching git tags
|
|
60
|
+
tag_pattern = "v[0-9]*"
|
|
61
|
+
# regex for skipping tags
|
|
62
|
+
skip_tags = ""
|
|
63
|
+
# regex for ignoring tags
|
|
64
|
+
ignore_tags = ""
|
|
65
|
+
# sort the tags topologically
|
|
66
|
+
topo_order = false
|
|
67
|
+
# sort the commits inside sections by oldest/newest order
|
|
68
|
+
sort_commits = "oldest"
|
|
69
|
+
|
|
70
|
+
[bump]
|
|
71
|
+
# Pre-1.0 semver: a feature bumps the minor, a breaking change also bumps the
|
|
72
|
+
# minor (not the major) while we're on 0.x. Fixes bump the patch.
|
|
73
|
+
features_always_bump_minor = true
|
|
74
|
+
breaking_always_bump_major = false
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing to RubyGems happens from a laptop (rake release:publish). This
|
|
4
|
+
# workflow only cuts the GitHub Release from the pushed tag — it never pushes to
|
|
5
|
+
# a registry, so it can't race the local publish. workflow_dispatch is kept as a
|
|
6
|
+
# manual escape hatch.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- 'v*'
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: write
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
release:
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
with:
|
|
23
|
+
fetch-depth: 0
|
|
24
|
+
|
|
25
|
+
- name: Set up Ruby
|
|
26
|
+
uses: ruby/setup-ruby@v1
|
|
27
|
+
with:
|
|
28
|
+
ruby-version: '3.2'
|
|
29
|
+
bundler-cache: true
|
|
30
|
+
|
|
31
|
+
- name: Extract version from tag
|
|
32
|
+
id: version
|
|
33
|
+
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
|
|
34
|
+
|
|
35
|
+
# Build the gem only to attach it to the GitHub Release — it is NOT pushed
|
|
36
|
+
# to RubyGems here (the laptop already did that via rake release:publish).
|
|
37
|
+
- name: Build gem
|
|
38
|
+
run: gem build flow_chat.gemspec
|
|
39
|
+
|
|
40
|
+
- name: Install git-cliff
|
|
41
|
+
uses: taiki-e/install-action@v2
|
|
42
|
+
with:
|
|
43
|
+
tool: git-cliff
|
|
44
|
+
|
|
45
|
+
- name: Generate release notes
|
|
46
|
+
run: |
|
|
47
|
+
git-cliff --config .cliff.toml --tag ${{ github.ref_name }} --strip all > RELEASE_NOTES.md
|
|
48
|
+
|
|
49
|
+
- name: Create GitHub Release
|
|
50
|
+
uses: softprops/action-gh-release@v1
|
|
51
|
+
with:
|
|
52
|
+
body_path: RELEASE_NOTES.md
|
|
53
|
+
files: |
|
|
54
|
+
flow_chat-${{ steps.version.outputs.VERSION }}.gem
|
|
55
|
+
env:
|
|
56
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.9.0] - 2026-07-09
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- Multi-platform gateways, composition architecture, and async processing ([#1](https://github.com/radioactive-labs/flow_chat/issues/1))
|
|
10
|
+
|
|
11
|
+
### Miscellaneous Tasks
|
|
12
|
+
|
|
13
|
+
- Add git-cliff changelog and release rake tasks
|
|
14
|
+
- Add security policy and require RubyGems MFA
|
|
15
|
+
|
|
16
|
+
### Styling
|
|
17
|
+
|
|
18
|
+
- Adopt StandardRB and format the codebase
|
|
19
|
+
|
|
20
|
+
## [0.1.0] - 2024-04-04
|
|
21
|
+
|
|
22
|
+
<!-- generated by git-cliff -->
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Development Commands
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
- `rake test` or `bundle exec rake test` - Run all tests
|
|
9
|
+
- `ruby -Itest test/unit/specific_test.rb` - Run a single test file
|
|
10
|
+
- `ruby -Itest test/unit/specific_test.rb -n test_method_name` - Run a specific test method
|
|
11
|
+
|
|
12
|
+
### Gem Development
|
|
13
|
+
- `bundle install` - Install dependencies
|
|
14
|
+
- `rake build` - Build the gem
|
|
15
|
+
- `rake release` - Build and release the gem (requires proper credentials)
|
|
16
|
+
|
|
17
|
+
### Rails Integration Testing
|
|
18
|
+
- Use `rails runner` instead of `rails console` for scripting
|
|
19
|
+
- For production logs, use Rails.logger with block syntax: `Rails.logger.warn { "message" }`
|
|
20
|
+
|
|
21
|
+
## Architecture Overview
|
|
22
|
+
|
|
23
|
+
FlowChat is a Rails framework for building conversational interfaces across multiple platforms (USSD, WhatsApp, Telegram, HTTP) using a **composition-based architecture** with **pluggable gateways**.
|
|
24
|
+
|
|
25
|
+
### Core Components
|
|
26
|
+
|
|
27
|
+
#### Processor (`lib/flow_chat/processor.rb`)
|
|
28
|
+
- Central orchestrator that builds and executes the middleware stack
|
|
29
|
+
- Configures gateways, session stores, and middleware
|
|
30
|
+
- Entry point: `FlowChat::Processor.new(controller) do |config|`
|
|
31
|
+
|
|
32
|
+
#### Gateway
|
|
33
|
+
- Platform-specific request/response handling
|
|
34
|
+
- Built-in gateways: `FlowChat::Ussd::Gateway::Nalo`, `FlowChat::Whatsapp::Gateway::CloudApi`, `FlowChat::Telegram::Gateway::BotApi`, `FlowChat::Http::Gateway::Simple`
|
|
35
|
+
- Custom gateways implement: `initialize(app, *args)` and `call(context)`
|
|
36
|
+
|
|
37
|
+
#### App (`lib/flow_chat/app.rb`)
|
|
38
|
+
- Unified interface for flows to interact with users
|
|
39
|
+
- Key method: `screen(key) { |prompt| ... }` for conversation logic
|
|
40
|
+
- Platform-agnostic accessors: `msisdn`, `user_id`, `platform`, etc.
|
|
41
|
+
|
|
42
|
+
#### Flow (`lib/flow_chat/flow.rb`)
|
|
43
|
+
- Simple base class containing conversation logic
|
|
44
|
+
- Initialize with `app` instance, implement flow methods
|
|
45
|
+
|
|
46
|
+
#### Session (`lib/flow_chat/session/`)
|
|
47
|
+
- Configurable session boundaries: `:flow`, `:platform`, `:gateway`, `:url`
|
|
48
|
+
- Session stores: `CacheSessionStore`
|
|
49
|
+
- Session IDs generated based on boundaries and identifiers
|
|
50
|
+
|
|
51
|
+
#### Factory Pattern (`lib/flow_chat/factory.rb`)
|
|
52
|
+
- Centralized processor configuration system
|
|
53
|
+
- Register configurations once, use everywhere
|
|
54
|
+
- Methods: `register(name, &block)`, `execute(name, controller:)`
|
|
55
|
+
- Eliminates duplication between webhook and background contexts
|
|
56
|
+
- Works seamlessly with `GenericAsyncJob` for async processing
|
|
57
|
+
|
|
58
|
+
#### Async Background Processing (`lib/flow_chat/async_job.rb`, `gateway_async_support.rb`, `generic_async_job.rb`)
|
|
59
|
+
- Decouple flow processing from webhook request-response cycles
|
|
60
|
+
- Base class: `FlowChat::AsyncJob` for custom background jobs
|
|
61
|
+
- `GenericAsyncJob`: Built-in job that uses Factory pattern (no custom job needed)
|
|
62
|
+
- `BackgroundController` mimics controller interface in background context
|
|
63
|
+
- `GatewayAsyncSupport` concern for gateways to detect and enqueue async jobs
|
|
64
|
+
- Automatic detection: async enqueue, background execute, or inline processing
|
|
65
|
+
- Supported gateways: WhatsApp Cloud API, Intercom API, HTTP Simple (not USSD)
|
|
66
|
+
|
|
67
|
+
### Middleware Stack Architecture
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
Gateway -> Session::Middleware -> Custom Middleware -> Executor -> Flow
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Gateway**: Parses platform-specific requests, renders responses
|
|
74
|
+
- **Session::Middleware**: Manages session boundaries and storage
|
|
75
|
+
- **Custom Middleware**: Business logic, authentication, logging
|
|
76
|
+
- **Executor**: Instantiates flows and handles interrupts
|
|
77
|
+
- **Flow**: Business logic using `app.screen()` for conversation
|
|
78
|
+
|
|
79
|
+
### Key Patterns
|
|
80
|
+
|
|
81
|
+
#### Screen-Based Navigation
|
|
82
|
+
```ruby
|
|
83
|
+
def registration_flow
|
|
84
|
+
email = app.screen(:email) { |p| p.ask "Enter email:", validate: email_validator }
|
|
85
|
+
name = app.screen(:name) { |p| p.ask "Enter name:", transform: ->(input) { input.titleize } }
|
|
86
|
+
app.say "Welcome #{name}!"
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Platform Configuration
|
|
91
|
+
```ruby
|
|
92
|
+
processor = FlowChat::Processor.new(self) do |config|
|
|
93
|
+
config.use_gateway FlowChat::Ussd::Gateway::Nalo
|
|
94
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
95
|
+
config.use_session_config(boundaries: [:flow], identifier: :msisdn)
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
#### Multi-Platform Support
|
|
100
|
+
Same flow code works across USSD, WhatsApp, Telegram, and HTTP by using platform-agnostic `app.screen()` calls.
|
|
101
|
+
|
|
102
|
+
#### Factory Pattern with Async
|
|
103
|
+
The recommended approach for async processing using centralized configuration:
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Register factory once in initializer
|
|
107
|
+
FlowChat::Factory.register :whatsapp do |controller|
|
|
108
|
+
processor = FlowChat::Processor.new(controller) do |config|
|
|
109
|
+
config.use_gateway FlowChat::Whatsapp::Gateway::CloudApi
|
|
110
|
+
config.use_session_store FlowChat::Session::CacheSessionStore
|
|
111
|
+
config.use_async(factory: :whatsapp) # Self-referencing for async
|
|
112
|
+
end
|
|
113
|
+
processor.run(WhatsAppFlow, :start)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Use in webhook controller - one line!
|
|
117
|
+
FlowChat::Factory.execute(:whatsapp, controller: self)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**How it works:**
|
|
121
|
+
1. Webhook calls `Factory.execute(:whatsapp)`
|
|
122
|
+
2. Factory builds processor with `use_async(factory: :whatsapp)`
|
|
123
|
+
3. Gateway enqueues `GenericAsyncJob` with `factory: :whatsapp` param
|
|
124
|
+
4. Background job executes `Factory.execute(:whatsapp)` again
|
|
125
|
+
5. Gateway detects background context and processes inline
|
|
126
|
+
|
|
127
|
+
**Benefits:**
|
|
128
|
+
- No custom job class needed (`GenericAsyncJob` handles it automatically)
|
|
129
|
+
- Configuration defined once, works in both webhook and background contexts
|
|
130
|
+
- Webhook returns immediately (< 100ms), flow processes in background
|
|
131
|
+
- Automatic prevention of double-enqueueing
|
|
132
|
+
|
|
133
|
+
See [docs/factory-pattern.md](docs/factory-pattern.md) and [docs/async-background-processing.md](docs/async-background-processing.md) for details.
|
|
134
|
+
|
|
135
|
+
#### Custom Async Jobs
|
|
136
|
+
For advanced cases, create custom job classes with job params:
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
class MyFlowJob < FlowChat::AsyncJob
|
|
140
|
+
def execute(controller, **job_params)
|
|
141
|
+
deployment_id = job_params[:deployment_id]
|
|
142
|
+
# ... custom logic with job params
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Use with job params
|
|
147
|
+
config.use_async(MyFlowJob, deployment_id: 123)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## File Structure
|
|
151
|
+
|
|
152
|
+
### Core Library (`lib/flow_chat/`)
|
|
153
|
+
- `processor.rb` - Main orchestrator, middleware stack builder
|
|
154
|
+
- `app.rb` - Unified conversation interface
|
|
155
|
+
- `flow.rb` - Base flow class
|
|
156
|
+
- `executor.rb` - Flow execution and interrupt handling
|
|
157
|
+
- `context.rb` - Request context management
|
|
158
|
+
- `config.rb` - Global configuration
|
|
159
|
+
- `factory.rb` - Centralized processor configuration registry
|
|
160
|
+
- `async_job.rb` - Background processing base class and controllers
|
|
161
|
+
- `generic_async_job.rb` - Factory-based async job (no custom class needed)
|
|
162
|
+
- `gateway_async_support.rb` - Async detection and enqueueing concern for gateways
|
|
163
|
+
|
|
164
|
+
### Platform Gateways
|
|
165
|
+
- `ussd/gateway/nalo.rb` - USSD platform integration (async not supported)
|
|
166
|
+
- `whatsapp/gateway/cloud_api.rb` - WhatsApp Business API integration (async supported)
|
|
167
|
+
- `telegram/gateway/bot_api.rb` - Telegram Bot API integration (async supported)
|
|
168
|
+
- `http/gateway/simple.rb` - HTTP/JSON API integration (async supported)
|
|
169
|
+
- `intercom/gateway/intercom_api.rb` - Intercom customer support integration (async supported)
|
|
170
|
+
- All gateways include `GatewayAsyncSupport` concern for unified async handling
|
|
171
|
+
|
|
172
|
+
### Session Management (`session/`)
|
|
173
|
+
- `middleware.rb` - Session boundary and ID generation
|
|
174
|
+
- `rails_session_store.rb` - Rails session integration
|
|
175
|
+
- `cache_session_store.rb` - Rails cache integration
|
|
176
|
+
|
|
177
|
+
### Platform-Specific (`ussd/`, `whatsapp/`, `telegram/`, `http/`, `intercom/`)
|
|
178
|
+
- `renderer.rb` - Platform-specific response formatting
|
|
179
|
+
- `middleware/` - Platform-specific processing logic
|
|
180
|
+
- `intercom/client.rb` - Intercom REST API integration
|
|
181
|
+
- `intercom/configuration.rb` - Intercom credentials and settings
|
|
182
|
+
- `intercom/conversation_manager.rb` - Generic conversation management utilities
|
|
183
|
+
|
|
184
|
+
### Testing (`test/`)
|
|
185
|
+
- `test_helper.rb` - Test setup with mock Rails environment
|
|
186
|
+
- `unit/` - Unit tests for individual components
|
|
187
|
+
- `integration/` - Integration tests for full flow scenarios
|
|
188
|
+
- `e2e/` - End-to-end tests for platform-specific features
|
|
189
|
+
|
|
190
|
+
## Testing Approach
|
|
191
|
+
|
|
192
|
+
- Uses Minitest with custom test helpers
|
|
193
|
+
- Mock Rails environment for testing without full Rails app
|
|
194
|
+
- Test flows using `mock_controller` and session store mocks
|
|
195
|
+
- Platform-specific tests verify gateway behavior
|
|
196
|
+
- Integration tests verify full request/response cycles
|
|
197
|
+
|
|
198
|
+
## Configuration Patterns
|
|
199
|
+
|
|
200
|
+
### Global Configuration
|
|
201
|
+
```ruby
|
|
202
|
+
# Logger injection into middleware stack (defaults to true in Rails development)
|
|
203
|
+
FlowChat::Config.inject_middleware_logger = true
|
|
204
|
+
|
|
205
|
+
# Other global configs
|
|
206
|
+
FlowChat::Config.logger = Rails.logger
|
|
207
|
+
FlowChat::Config.combine_validation_error_with_message = true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Session Configuration
|
|
211
|
+
```ruby
|
|
212
|
+
config.use_session_config(
|
|
213
|
+
boundaries: [:flow, :platform], # Session isolation
|
|
214
|
+
identifier: :msisdn, # Session key type
|
|
215
|
+
hash_identifiers: true # Privacy protection
|
|
216
|
+
)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Multi-Tenancy Support
|
|
220
|
+
```ruby
|
|
221
|
+
config.use_url_isolation # tenant1.app.com vs tenant2.app.com
|
|
222
|
+
config.use_cross_platform_sessions # Share sessions between USSD/WhatsApp
|
|
223
|
+
config.use_durable_sessions # Use user_id instead of request_id
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Intercom Integration
|
|
227
|
+
|
|
228
|
+
FlowChat provides comprehensive Intercom integration for customer support workflows with proper webhook validation and API client functionality.
|
|
229
|
+
|
|
230
|
+
#### Configuration
|
|
231
|
+
```ruby
|
|
232
|
+
# Rails credentials (config/credentials.yml.enc)
|
|
233
|
+
intercom:
|
|
234
|
+
access_token: "your_intercom_access_token"
|
|
235
|
+
client_secret: "your_intercom_client_secret"
|
|
236
|
+
skip_signature_validation: false # Optional: disable webhook validation for testing
|
|
237
|
+
|
|
238
|
+
# Or environment variables
|
|
239
|
+
INTERCOM_ACCESS_TOKEN=your_intercom_access_token
|
|
240
|
+
INTERCOM_CLIENT_SECRET=your_intercom_client_secret
|
|
241
|
+
INTERCOM_SKIP_SIGNATURE_VALIDATION=false
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Gateway Setup
|
|
245
|
+
```ruby
|
|
246
|
+
# Basic Intercom gateway setup (default webhook topics)
|
|
247
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi
|
|
248
|
+
config.use_session_config(boundaries: [:conversation], identifier: :conversation_id)
|
|
249
|
+
|
|
250
|
+
# With custom configuration
|
|
251
|
+
intercom_config = FlowChat::Intercom::Configuration.get(:my_config)
|
|
252
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, intercom_config
|
|
253
|
+
|
|
254
|
+
# Additional webhook topics (e.g., to include admin events)
|
|
255
|
+
# Note: Default topics (user.created, user.replied) are always included
|
|
256
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, nil, [
|
|
257
|
+
"conversation.admin.assigned",
|
|
258
|
+
"conversation.admin.replied"
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
# Custom config AND additional webhook topics
|
|
262
|
+
config.use_gateway FlowChat::Intercom::Gateway::IntercomApi, intercom_config, [
|
|
263
|
+
"conversation.admin.assigned",
|
|
264
|
+
"conversation.admin.replied"
|
|
265
|
+
]
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Default Webhook Topics:**
|
|
269
|
+
- `conversation.user.created` - New conversation started by user
|
|
270
|
+
- `conversation.user.replied` - User replied in existing conversation
|
|
271
|
+
|
|
272
|
+
**Additional Available Topics:**
|
|
273
|
+
- `conversation.admin.assigned` - Admin assigned to conversation
|
|
274
|
+
- `conversation.admin.replied` - Admin replied to conversation
|
|
275
|
+
- `conversation.admin.closed` - Admin closed conversation
|
|
276
|
+
- See [Intercom webhook docs](https://developers.intercom.com/docs/references/webhooks/webhook-models/) for full list
|
|
277
|
+
|
|
278
|
+
#### Webhook Setup
|
|
279
|
+
1. Add your HTTPS endpoint URL in Intercom Developer Hub → Configure → Webhooks
|
|
280
|
+
2. Intercom validates your endpoint with HEAD request (handled automatically)
|
|
281
|
+
3. Webhook notifications are validated using X-Hub-Signature with client_secret
|
|
282
|
+
|
|
283
|
+
#### FlowChat Client API
|
|
284
|
+
The FlowChat Intercom client provides only the core methods needed by the gateway:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# Access within a flow via context
|
|
288
|
+
client = context["intercom.client"]
|
|
289
|
+
conversation_id = context["request.conversation_id"]
|
|
290
|
+
|
|
291
|
+
# Send a message (used by gateway automatically)
|
|
292
|
+
client.send_message(conversation_id, "Hello!", choices: nil, media: nil)
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
**Note:** Conversations are automatically assigned to the configured admin when messages are sent (Intercom's default behavior). Each admin can change this in their personal settings if desired.
|
|
296
|
+
|
|
297
|
+
**Important:** For business logic (tags, assignment, state management, fetching conversations), use the official `intercom` gem directly in your application:
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
# In your application code, use the official gem for business logic
|
|
301
|
+
intercom = Intercom::Client.new(token: access_token)
|
|
302
|
+
|
|
303
|
+
# Tag management
|
|
304
|
+
intercom.tags.tag(name: "AI_HANDLING", conversations: [{id: conversation_id}])
|
|
305
|
+
|
|
306
|
+
# Assignment (override default auto-assignment)
|
|
307
|
+
intercom.conversations.reply(id: conversation_id, message_type: "assignment", admin_id: admin_id)
|
|
308
|
+
|
|
309
|
+
# State management
|
|
310
|
+
intercom.conversations.reply(id: conversation_id, message_type: "closed")
|
|
311
|
+
|
|
312
|
+
# Fetch conversation details
|
|
313
|
+
conversation = intercom.conversations.find(id: conversation_id)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
#### Error Handling
|
|
317
|
+
- Rate limiting: `RateLimitError` with retry-after information
|
|
318
|
+
- Authentication: `ConfigurationError` for invalid tokens
|
|
319
|
+
- API errors: Proper handling for Intercom gem exceptions (ResourceNotFound, AuthenticationError, etc.)
|
|
320
|
+
|
|
321
|
+
## Instrumentation
|
|
322
|
+
|
|
323
|
+
FlowChat includes comprehensive instrumentation via `FlowChat::Instrumentation`:
|
|
324
|
+
- Flow execution events
|
|
325
|
+
- Session creation events
|
|
326
|
+
- Platform-specific metrics
|
|
327
|
+
- Error tracking and logging
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Contributing to Plutonium
|
|
2
|
+
|
|
3
|
+
## Commit Message Convention
|
|
4
|
+
|
|
5
|
+
This project follows [Conventional Commits](https://www.conventionalcommits.org/) for automated changelog generation and versioning.
|
|
6
|
+
|
|
7
|
+
### Format
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
<type>[optional scope]: <description>
|
|
11
|
+
|
|
12
|
+
[optional body]
|
|
13
|
+
|
|
14
|
+
[optional footer(s)]
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Types
|
|
18
|
+
|
|
19
|
+
- **feat**: A new feature (triggers MINOR version bump)
|
|
20
|
+
- **fix**: A bug fix (triggers PATCH version bump)
|
|
21
|
+
- **docs**: Documentation only changes
|
|
22
|
+
- **style**: Changes that don't affect code meaning (formatting, etc)
|
|
23
|
+
- **refactor**: Code change that neither fixes a bug nor adds a feature
|
|
24
|
+
- **perf**: Performance improvement
|
|
25
|
+
- **test**: Adding or updating tests
|
|
26
|
+
- **chore**: Maintenance tasks (dependencies, tooling, etc)
|
|
27
|
+
|
|
28
|
+
### Breaking Changes
|
|
29
|
+
|
|
30
|
+
Add `BREAKING CHANGE:` in the footer or `!` after type to trigger a MAJOR version bump:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
feat!: remove deprecated API
|
|
34
|
+
|
|
35
|
+
BREAKING CHANGE: The old API has been removed. Use new API instead.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Examples
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Feature (bumps 0.26.11 -> 0.27.0)
|
|
42
|
+
git commit -m "feat: add field-level options support for input definitions"
|
|
43
|
+
|
|
44
|
+
# Bug fix (bumps 0.26.11 -> 0.26.12)
|
|
45
|
+
git commit -m "fix: resolve inheritance issue with controller_for"
|
|
46
|
+
|
|
47
|
+
# Breaking change (bumps 0.26.11 -> 1.0.0)
|
|
48
|
+
git commit -m "feat!: redesign definition DSL
|
|
49
|
+
|
|
50
|
+
BREAKING CHANGE: The definition DSL has been completely redesigned.
|
|
51
|
+
See migration guide for details."
|
|
52
|
+
|
|
53
|
+
# With scope
|
|
54
|
+
git commit -m "feat(ui): add new table component"
|
|
55
|
+
git commit -m "fix(forms): correct hint display on validation errors"
|
|
56
|
+
|
|
57
|
+
# Documentation
|
|
58
|
+
git commit -m "docs: update definition structure guide"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Release Process
|
|
62
|
+
|
|
63
|
+
### Option 1: Automated (Recommended)
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
# See what the next version should be
|
|
67
|
+
rake release:next_version
|
|
68
|
+
|
|
69
|
+
# Prepare a new release (updates version, generates changelog)
|
|
70
|
+
rake release:prepare[0.27.0]
|
|
71
|
+
|
|
72
|
+
# Review changes
|
|
73
|
+
git diff
|
|
74
|
+
|
|
75
|
+
# Full automated release (prepare, commit, tag, push, publish)
|
|
76
|
+
rake release:full[0.27.0]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Option 2: Manual
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 1. Update version in lib/plutonium/version.rb
|
|
83
|
+
# 2. Generate changelog
|
|
84
|
+
git-cliff --tag v0.27.0 -o CHANGELOG.md
|
|
85
|
+
|
|
86
|
+
# 3. Commit and tag
|
|
87
|
+
git add -A
|
|
88
|
+
git commit -m "chore(release): prepare for v0.27.0"
|
|
89
|
+
git tag v0.27.0
|
|
90
|
+
git push origin main --tags
|
|
91
|
+
|
|
92
|
+
# 4. GitHub Actions will automatically publish to RubyGems
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Version Bumping Rules
|
|
96
|
+
|
|
97
|
+
Following semantic versioning:
|
|
98
|
+
|
|
99
|
+
- **MAJOR** (X.0.0): Breaking changes
|
|
100
|
+
- **MINOR** (0.X.0): New features (backwards compatible)
|
|
101
|
+
- **PATCH** (0.0.X): Bug fixes (backwards compatible)
|
|
102
|
+
|
|
103
|
+
The automation determines the version bump based on commits since the last tag:
|
|
104
|
+
- Any commit with `BREAKING CHANGE:` or `!` after type → MAJOR
|
|
105
|
+
- Any `feat:` commits → MINOR
|
|
106
|
+
- Any `fix:` commits → PATCH
|
|
107
|
+
|
|
108
|
+
## Development Setup
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# Install dependencies
|
|
112
|
+
bundle install
|
|
113
|
+
|
|
114
|
+
# Run tests
|
|
115
|
+
bundle exec rspec
|
|
116
|
+
|
|
117
|
+
# Install git-cliff for changelog generation (optional)
|
|
118
|
+
brew install git-cliff # macOS
|
|
119
|
+
# or
|
|
120
|
+
cargo install git-cliff # via Rust
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Pull Request Process
|
|
124
|
+
|
|
125
|
+
1. Fork the repository
|
|
126
|
+
2. Create a feature branch: `git checkout -b feat/my-feature`
|
|
127
|
+
3. Make your changes with conventional commits
|
|
128
|
+
4. Run tests: `bundle exec rspec`
|
|
129
|
+
5. Push and create a pull request
|
|
130
|
+
6. The PR title should also follow conventional commit format
|
|
131
|
+
|
|
132
|
+
## Questions?
|
|
133
|
+
|
|
134
|
+
Open an issue or discussion on GitHub!
|
data/Gemfile
CHANGED