support_desk 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +197 -0
- data/README.md +1131 -14
- data/app/assets/stylesheets/support_desk.css +10 -0
- data/app/controllers/support_desk/tickets_controller.rb +23 -1
- data/app/helpers/support_desk/engine_helper.rb +16 -0
- data/app/views/support_desk/console/tickets/_actions.html.erb +15 -0
- data/app/views/support_desk/console/tickets/_assignment.html.erb +16 -2
- data/app/views/support_desk/console/tickets/_composer.html.erb +27 -0
- data/app/views/support_desk/console/tickets/_context_card.html.erb +28 -0
- data/app/views/support_desk/console/tickets/_draft.html.erb +114 -0
- data/app/views/support_desk/console/tickets/_message.html.erb +34 -1
- data/app/views/support_desk/console/tickets/_ticket_row.html.erb +20 -0
- data/app/views/support_desk/console/tickets/_timeline.html.erb +68 -14
- data/app/views/support_desk/console/tickets/show.html.erb +5 -0
- data/app/views/support_desk/tickets/_human_door.html.erb +24 -0
- data/app/views/support_desk/tickets/_ticket_row.html.erb +13 -0
- data/config/locales/support_desk.console.en.yml +63 -0
- data/config/locales/support_desk.console.es.yml +65 -0
- data/config/locales/support_desk.en.yml +15 -0
- data/config/locales/support_desk.es.yml +23 -0
- data/config/routes.rb +7 -1
- data/lib/generators/support_desk/assistant_generator.rb +193 -0
- data/lib/generators/support_desk/install_generator.rb +21 -3
- data/lib/generators/support_desk/templates/add_assistants_to_support_desk.rb.erb +236 -0
- data/lib/generators/support_desk/templates/assistant/service.rb.erb +60 -0
- data/lib/generators/support_desk/templates/assistant/turn_job.rb.erb +72 -0
- data/lib/generators/support_desk/templates/assistant/turn_job_test.rb.erb +69 -0
- data/lib/generators/support_desk/templates/create_support_desk_message_registrations.rb.erb +42 -0
- data/lib/generators/support_desk/templates/initializer.rb +33 -0
- data/lib/generators/support_desk/upgrade_generator.rb +25 -7
- data/lib/support_desk/assistant_policy.rb +213 -0
- data/lib/support_desk/brief.rb +283 -0
- data/lib/support_desk/configuration.rb +552 -2
- data/lib/support_desk/console.rb +290 -8
- data/lib/support_desk/context_card.rb +10 -1
- data/lib/support_desk/doctor.rb +171 -1
- data/lib/support_desk/engine.rb +16 -1
- data/lib/support_desk/errors.rb +37 -0
- data/lib/support_desk/events.rb +12 -5
- data/lib/support_desk/macros.rb +14 -1
- data/lib/support_desk/message_signatures.rb +20 -0
- data/lib/support_desk/models/assistant.rb +188 -0
- data/lib/support_desk/models/concerns/requester.rb +18 -0
- data/lib/support_desk/models/desk.rb +26 -3
- data/lib/support_desk/models/draft.rb +275 -0
- data/lib/support_desk/models/event.rb +15 -1
- data/lib/support_desk/models/message_registration.rb +12 -0
- data/lib/support_desk/models/ticket/assistance.rb +899 -0
- data/lib/support_desk/models/ticket.rb +406 -58
- data/lib/support_desk/outcome.rb +38 -0
- data/lib/support_desk/queue.rb +41 -5
- data/lib/support_desk/test_helpers.rb +152 -0
- data/lib/support_desk/timeline.rb +40 -11
- data/lib/support_desk/topic.rb +22 -0
- data/lib/support_desk/topic_tree.rb +7 -1
- data/lib/support_desk/transcript.rb +234 -0
- data/lib/support_desk/version.rb +1 -1
- data/lib/support_desk.rb +151 -0
- data/lib/tasks/support_desk.rake +57 -0
- metadata +33 -8
data/lib/support_desk/version.rb
CHANGED
data/lib/support_desk.rb
CHANGED
|
@@ -11,6 +11,7 @@ require "chats"
|
|
|
11
11
|
require_relative "support_desk/version"
|
|
12
12
|
require_relative "support_desk/errors"
|
|
13
13
|
require_relative "support_desk/events"
|
|
14
|
+
require_relative "support_desk/assistant_policy"
|
|
14
15
|
require_relative "support_desk/topic"
|
|
15
16
|
require_relative "support_desk/topic_tree"
|
|
16
17
|
require_relative "support_desk/configuration"
|
|
@@ -93,6 +94,7 @@ module SupportDesk
|
|
|
93
94
|
@configured = false
|
|
94
95
|
@subscribers = nil
|
|
95
96
|
@desks = nil
|
|
97
|
+
@assistants = nil
|
|
96
98
|
self
|
|
97
99
|
end
|
|
98
100
|
|
|
@@ -103,6 +105,142 @@ module SupportDesk
|
|
|
103
105
|
self
|
|
104
106
|
end
|
|
105
107
|
|
|
108
|
+
# --- Assistants -----------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
# The assistant record for +key+, memoised per process — or the desk's
|
|
111
|
+
# default when called with nothing. nil when this installation has no
|
|
112
|
+
# assistant at all, which is the answer every existing host gets.
|
|
113
|
+
#
|
|
114
|
+
# Found or created exactly like a desk, and never INSERT-first: an
|
|
115
|
+
# assistant is read on every transition and written once in her life.
|
|
116
|
+
def assistant(key = nil)
|
|
117
|
+
key = (key || config.default_assistant_key)
|
|
118
|
+
return nil if key.nil?
|
|
119
|
+
|
|
120
|
+
key = key.to_sym
|
|
121
|
+
unless config.assistant?(key)
|
|
122
|
+
raise ConfigurationError,
|
|
123
|
+
"no assistant #{key.inspect} is configured — declare it with " \
|
|
124
|
+
"`config.assistant #{key.inspect} do |assistant| … end`"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Resolving her is also when what she is CALLED is written down, so a
|
|
128
|
+
# message she signed still says the same thing after somebody takes
|
|
129
|
+
# her out of the initializer (R8). It writes only when configuration
|
|
130
|
+
# and the row disagree.
|
|
131
|
+
(assistants[key] ||= Assistant.for(key)).snapshot_disclosure!
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Every assistant record this process has resolved, keyed by key.
|
|
135
|
+
def assistants # :nodoc:
|
|
136
|
+
@assistants ||= {}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Forget the memoised Assistant records without touching configuration —
|
|
140
|
+
# for tests that truncate tables between examples.
|
|
141
|
+
def reset_assistants!
|
|
142
|
+
@assistants = nil
|
|
143
|
+
self
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Whether +record+ is a machine rather than a person. Duck-typed, because
|
|
147
|
+
# a host's own `acts_as_support_agent kind: :ai` model answers it too —
|
|
148
|
+
# and is then refused everywhere, which is the point (I2).
|
|
149
|
+
def ai_actor?(record)
|
|
150
|
+
record.respond_to?(:support_agent_kind) && record.support_agent_kind == :ai
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Give back every seat an assistant can no longer sit in — she was
|
|
154
|
+
# switched off, her configuration was removed, or her policy no longer
|
|
155
|
+
# lets her hold a case — and ask for a person on each of them.
|
|
156
|
+
#
|
|
157
|
+
# Invalid-assignee recovery, NOT silence detection: it reads the seats
|
|
158
|
+
# that exist rather than the assistants a running process happens to
|
|
159
|
+
# have configured, so a `deactivate!`, a flag flipped off and a topic
|
|
160
|
+
# cap tightened are all covered, with no `responds_within` and no
|
|
161
|
+
# overdue clock anywhere in it (R6). `release_silent_assistants!` runs
|
|
162
|
+
# it first; `rake support_desk:reclaim_assistant_seats` runs it alone.
|
|
163
|
+
#
|
|
164
|
+
# Returns how many seats it reclaimed.
|
|
165
|
+
def reclaim_assistant_seats!
|
|
166
|
+
return 0 unless Assistant.table_exists?
|
|
167
|
+
|
|
168
|
+
reclaimed = 0
|
|
169
|
+
Ticket.open.held_by_assistants.find_each do |ticket|
|
|
170
|
+
reclaimed += 1 if ticket.reclaim_assistant_seat!
|
|
171
|
+
rescue StandardError => e
|
|
172
|
+
report_error(e, context: { hook: :reclaim_assistant_seats, ticket: ticket.id })
|
|
173
|
+
end
|
|
174
|
+
logger&.info("[support_desk] reclaimed #{reclaimed} stranded assistant seat(s)") if reclaimed.positive?
|
|
175
|
+
reclaimed
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Release every assistant who has sat on a case longer than her
|
|
179
|
+
# `responds_within` without answering — and ask for a person on it.
|
|
180
|
+
#
|
|
181
|
+
# This is the net under a dead harness: a queue worker that stopped, a
|
|
182
|
+
# model provider that is down, a job that raised its last retry away.
|
|
183
|
+
# Run it every minute (`rake support_desk:release_silent_assistants`).
|
|
184
|
+
# Returns how many cases it moved.
|
|
185
|
+
def release_silent_assistants!
|
|
186
|
+
# A seat nobody can sit in any more is not a silence problem, and it
|
|
187
|
+
# must not need a `responds_within` to be noticed (R6).
|
|
188
|
+
moved = reclaim_assistant_seats!
|
|
189
|
+
config.assistants.each_key do |key|
|
|
190
|
+
agent = assistant(key)
|
|
191
|
+
window = agent&.responds_within
|
|
192
|
+
next if window.nil?
|
|
193
|
+
|
|
194
|
+
Ticket.open.assigned_to(agent).awaiting_reply.waiting_over(window).find_each do |ticket|
|
|
195
|
+
# Conditional, under the case's own lock: every predicate above was
|
|
196
|
+
# true when this row was SELECTED, and a person may have answered
|
|
197
|
+
# it since (R5).
|
|
198
|
+
moved += 1 if ticket.escalate_if_still_silent!(agent, window)
|
|
199
|
+
rescue StandardError => e
|
|
200
|
+
report_error(e, context: { hook: :release_silent_assistants, ticket: ticket.id })
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
logger&.info("[support_desk] silent assistants: #{moved} case(s) handed to a person") if moved.positive?
|
|
204
|
+
moved
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Re-emit `:assistant_turn` for cases whose turn nobody acted on — a
|
|
208
|
+
# harness that was down when the event fired, a job that was dropped.
|
|
209
|
+
#
|
|
210
|
+
# At-least-once on purpose: a duplicate turn is harmless, because the
|
|
211
|
+
# turn is consumed by the first action and every later one is a
|
|
212
|
+
# StaleTurn. Run it every five minutes. Returns how many it re-emitted.
|
|
213
|
+
def redispatch_assistant_turns!(older_than: 1.minute)
|
|
214
|
+
older_than = older_than.to_i.seconds unless older_than.respond_to?(:ago)
|
|
215
|
+
emitted = 0
|
|
216
|
+
|
|
217
|
+
config.desks.each_key do |key|
|
|
218
|
+
agent = desk(key)&.assistant
|
|
219
|
+
# Repair before deciding. A requester message whose registration was
|
|
220
|
+
# lost after commit leaves the clocks describing a case that no longer
|
|
221
|
+
# exists — and `assistant_idle_since` reads those clocks, so the case
|
|
222
|
+
# it most needs to find is the one it cannot see. Folding the message
|
|
223
|
+
# in COMMITS on its own and emits the turn it produces, so a dead
|
|
224
|
+
# process followed by nothing but this task still ends in an
|
|
225
|
+
# actionable turn (R3).
|
|
226
|
+
Ticket.for_desk(key).with_unregistered_requester_messages.find_each do |ticket|
|
|
227
|
+
emitted += 1 if ticket.reconcile_and_commit!.positive?
|
|
228
|
+
rescue StandardError => e
|
|
229
|
+
report_error(e, context: { hook: :redispatch_assistant_turns, ticket: ticket.id })
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
next if agent.nil?
|
|
233
|
+
|
|
234
|
+
Ticket.open.for_desk(key).assistant_idle_since(older_than.ago).find_each do |ticket|
|
|
235
|
+
next unless ticket.assistant_policy(agent).may_observe?
|
|
236
|
+
|
|
237
|
+
ticket.send(:emit_assistant_turn)
|
|
238
|
+
emitted += 1
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
emitted
|
|
242
|
+
end
|
|
243
|
+
|
|
106
244
|
# --- Desks ------------------------------------------------------------------
|
|
107
245
|
|
|
108
246
|
# The desk record for +key+, memoised per process.
|
|
@@ -224,6 +362,19 @@ module SupportDesk
|
|
|
224
362
|
defined?(::Rails) ? ::Rails.logger : nil
|
|
225
363
|
end
|
|
226
364
|
|
|
365
|
+
# Report an exception the way the event dispatcher does: `Rails.error`
|
|
366
|
+
# when there is one, the log otherwise. Extracted because every hook the
|
|
367
|
+
# gem runs on a host's behalf — a `hand_off_when` block, a turn
|
|
368
|
+
# emission, a sweep — has to report and carry on rather than take a
|
|
369
|
+
# transition down with it.
|
|
370
|
+
def report_error(error, context: {}) # :nodoc:
|
|
371
|
+
if defined?(Rails) && Rails.respond_to?(:error) && Rails.error
|
|
372
|
+
Rails.error.report(error, handled: true, source: "support_desk", context: context)
|
|
373
|
+
else
|
|
374
|
+
logger&.error("[support_desk] #{error.class}: #{error.message} #{context.inspect}")
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
|
|
227
378
|
# Where the host mounted the requester-facing engine
|
|
228
379
|
# ("/messages/support"), or nil when they haven't. Used for the grouped
|
|
229
380
|
# inbox row's link and by `doctor`.
|
data/lib/tasks/support_desk.rake
CHANGED
|
@@ -25,3 +25,60 @@ namespace :support_desk do
|
|
|
25
25
|
puts "[support_desk] #{updated} case(s) now point at their requester."
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
|
+
|
|
29
|
+
namespace :support_desk do
|
|
30
|
+
desc "Hand over every case an assistant has sat on without answering (run every minute)"
|
|
31
|
+
task release_silent_assistants: :environment do
|
|
32
|
+
# The net under a dead harness: a queue worker that stopped, a provider
|
|
33
|
+
# that is down, a job that spent its last retry. Each case it finds has
|
|
34
|
+
# its seat released AND a person asked for — a case that waited that
|
|
35
|
+
# long deserves one, whatever the assistant would have said.
|
|
36
|
+
moved = SupportDesk.release_silent_assistants!
|
|
37
|
+
|
|
38
|
+
puts "[support_desk] #{moved} case(s) handed to a person."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
desc "Give back every seat an assistant can no longer sit in (run every minute)"
|
|
42
|
+
task reclaim_assistant_seats: :environment do
|
|
43
|
+
# Invalid-assignee recovery: she was switched off, her configuration was
|
|
44
|
+
# taken away, or her policy no longer lets her hold a case. It needs no
|
|
45
|
+
# `responds_within` and no overdue clock — `release_silent_assistants`
|
|
46
|
+
# runs it first, and this is the same work on its own.
|
|
47
|
+
reclaimed = SupportDesk.reclaim_assistant_seats!
|
|
48
|
+
|
|
49
|
+
puts "[support_desk] #{reclaimed} stranded seat(s) given back to people."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "Re-emit the turn for cases nobody answered (OLDER_THAN=60 seconds; run every 5 minutes)"
|
|
53
|
+
task redispatch_assistant_turns: :environment do
|
|
54
|
+
# At-least-once, and that is safe: the turn is consumed by the first
|
|
55
|
+
# action, so a duplicate one is a StaleTurn and writes nothing.
|
|
56
|
+
older_than = (ENV["OLDER_THAN"] || 60).to_i.seconds
|
|
57
|
+
emitted = SupportDesk.redispatch_assistant_turns!(older_than: older_than)
|
|
58
|
+
|
|
59
|
+
puts "[support_desk] #{emitted} turn(s) re-emitted (idle for more than #{older_than.inspect})."
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc "Report assistant status without changing tickets"
|
|
63
|
+
task assistant_status: :environment do
|
|
64
|
+
if SupportDesk.config.assistants.empty?
|
|
65
|
+
puts "[support_desk] no assistant is configured."
|
|
66
|
+
next
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
SupportDesk.config.assistants.each_key do |key|
|
|
70
|
+
assistant = SupportDesk.assistant(key)
|
|
71
|
+
held = assistant.held_tickets.count
|
|
72
|
+
idle_window = assistant.responds_within
|
|
73
|
+
idle = if idle_window
|
|
74
|
+
SupportDesk::Ticket.open.assistant_idle_since(idle_window.ago).count
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
puts "[support_desk] #{key} (#{assistant.active? ? "active" : "inactive"}, #{assistant.autonomy}): " \
|
|
78
|
+
"#{held} case(s) held, #{idle || "—"} idle turn(s)"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
puts "[support_desk] #{SupportDesk::Ticket.open.needs_human.count} case(s) need a person, " \
|
|
82
|
+
"#{SupportDesk::Draft.pending.count} proposal(s) waiting to be reviewed."
|
|
83
|
+
end
|
|
84
|
+
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: support_desk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- rameerez
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-09-
|
|
10
|
+
date: 2026-09-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activerecord
|
|
@@ -106,11 +106,18 @@ dependencies:
|
|
|
106
106
|
description: 'support_desk adds a customer support desk to any Rails app: tickets
|
|
107
107
|
that are conversations. A requester asks for help about something in your app (a
|
|
108
108
|
ride, an order, a withdrawal) or about nothing in particular, your desk answers,
|
|
109
|
-
humans
|
|
110
|
-
|
|
111
|
-
and
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
humans sign the answers, and your team works a queue — alongside AI agents that
|
|
110
|
+
are first-class citizens of the desk: an assistant has a seat, a name, a turn budget
|
|
111
|
+
and a level (observe, draft, reply, resolve) enforced inside every transition; she
|
|
112
|
+
proposes replies a person sends, or answers herself where you let her, hands off
|
|
113
|
+
when unsure, and can never touch a human-held case, a capped topic or a customer
|
|
114
|
+
who asked for a person. The gem ships the policy, the drafts and review, the turn
|
|
115
|
+
that makes a late model answer harmless, disclosure and the two exits — and no LLM:
|
|
116
|
+
any provider, any prompt, in a ten-line job. It is a product gem on the `chats`
|
|
117
|
+
kernel: chats owns the transcript, realtime, attachments, read state and moderation;
|
|
118
|
+
support_desk owns cases, topics, assignment, routing and the console API. One macro
|
|
119
|
+
makes a model ask for help (`has_support_tickets`), one makes a domain record something
|
|
120
|
+
to ask about (`supportable`), one makes a person answer (`acts_as_support_agent`).
|
|
114
121
|
Topics are a tree defined in code that drives the wizard, the picker, routing and
|
|
115
122
|
metrics; assignment is a history, not a column; every transition writes an append-only
|
|
116
123
|
event row and emits an error-isolated event your app can subscribe to. Batteries
|
|
@@ -142,6 +149,7 @@ files:
|
|
|
142
149
|
- app/views/support_desk/console/tickets/_assignment.html.erb
|
|
143
150
|
- app/views/support_desk/console/tickets/_composer.html.erb
|
|
144
151
|
- app/views/support_desk/console/tickets/_context_card.html.erb
|
|
152
|
+
- app/views/support_desk/console/tickets/_draft.html.erb
|
|
145
153
|
- app/views/support_desk/console/tickets/_message.html.erb
|
|
146
154
|
- app/views/support_desk/console/tickets/_nav_badge.html.erb
|
|
147
155
|
- app/views/support_desk/console/tickets/_new_conversation_form.html.erb
|
|
@@ -154,6 +162,7 @@ files:
|
|
|
154
162
|
- app/views/support_desk/console/tickets/show.html.erb
|
|
155
163
|
- app/views/support_desk/tickets/_context_card.html.erb
|
|
156
164
|
- app/views/support_desk/tickets/_door.html.erb
|
|
165
|
+
- app/views/support_desk/tickets/_human_door.html.erb
|
|
157
166
|
- app/views/support_desk/tickets/_pick_thing.html.erb
|
|
158
167
|
- app/views/support_desk/tickets/_pick_topic.html.erb
|
|
159
168
|
- app/views/support_desk/tickets/_ticket_row.html.erb
|
|
@@ -172,16 +181,24 @@ files:
|
|
|
172
181
|
- gemfiles/rails_7.2.gemfile
|
|
173
182
|
- gemfiles/rails_8.0.gemfile
|
|
174
183
|
- gemfiles/rails_8.1.gemfile
|
|
184
|
+
- lib/generators/support_desk/assistant_generator.rb
|
|
175
185
|
- lib/generators/support_desk/console_generator.rb
|
|
176
186
|
- lib/generators/support_desk/install_generator.rb
|
|
187
|
+
- lib/generators/support_desk/templates/add_assistants_to_support_desk.rb.erb
|
|
177
188
|
- lib/generators/support_desk/templates/add_opened_by_to_support_desk_tickets.rb.erb
|
|
189
|
+
- lib/generators/support_desk/templates/assistant/service.rb.erb
|
|
190
|
+
- lib/generators/support_desk/templates/assistant/turn_job.rb.erb
|
|
191
|
+
- lib/generators/support_desk/templates/assistant/turn_job_test.rb.erb
|
|
178
192
|
- lib/generators/support_desk/templates/console/controller.rb.erb
|
|
179
193
|
- lib/generators/support_desk/templates/console/resource.rb.erb
|
|
194
|
+
- lib/generators/support_desk/templates/create_support_desk_message_registrations.rb.erb
|
|
180
195
|
- lib/generators/support_desk/templates/create_support_desk_tables.rb.erb
|
|
181
196
|
- lib/generators/support_desk/templates/initializer.rb
|
|
182
197
|
- lib/generators/support_desk/upgrade_generator.rb
|
|
183
198
|
- lib/generators/support_desk/views_generator.rb
|
|
184
199
|
- lib/support_desk.rb
|
|
200
|
+
- lib/support_desk/assistant_policy.rb
|
|
201
|
+
- lib/support_desk/brief.rb
|
|
185
202
|
- lib/support_desk/configuration.rb
|
|
186
203
|
- lib/support_desk/console.rb
|
|
187
204
|
- lib/support_desk/console_engine.rb
|
|
@@ -193,20 +210,27 @@ files:
|
|
|
193
210
|
- lib/support_desk/errors.rb
|
|
194
211
|
- lib/support_desk/events.rb
|
|
195
212
|
- lib/support_desk/macros.rb
|
|
213
|
+
- lib/support_desk/message_signatures.rb
|
|
196
214
|
- lib/support_desk/models/application_record.rb
|
|
197
215
|
- lib/support_desk/models/assignment.rb
|
|
216
|
+
- lib/support_desk/models/assistant.rb
|
|
198
217
|
- lib/support_desk/models/concerns/agent.rb
|
|
199
218
|
- lib/support_desk/models/concerns/requester.rb
|
|
200
219
|
- lib/support_desk/models/concerns/supportable.rb
|
|
201
220
|
- lib/support_desk/models/desk.rb
|
|
221
|
+
- lib/support_desk/models/draft.rb
|
|
202
222
|
- lib/support_desk/models/event.rb
|
|
223
|
+
- lib/support_desk/models/message_registration.rb
|
|
203
224
|
- lib/support_desk/models/ticket.rb
|
|
225
|
+
- lib/support_desk/models/ticket/assistance.rb
|
|
226
|
+
- lib/support_desk/outcome.rb
|
|
204
227
|
- lib/support_desk/queue.rb
|
|
205
228
|
- lib/support_desk/summary.rb
|
|
206
229
|
- lib/support_desk/test_helpers.rb
|
|
207
230
|
- lib/support_desk/timeline.rb
|
|
208
231
|
- lib/support_desk/topic.rb
|
|
209
232
|
- lib/support_desk/topic_tree.rb
|
|
233
|
+
- lib/support_desk/transcript.rb
|
|
210
234
|
- lib/support_desk/version.rb
|
|
211
235
|
- lib/support_desk/wizard.rb
|
|
212
236
|
- lib/tasks/support_desk.rake
|
|
@@ -236,5 +260,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
236
260
|
requirements: []
|
|
237
261
|
rubygems_version: 3.6.2
|
|
238
262
|
specification_version: 4
|
|
239
|
-
summary:
|
|
263
|
+
summary: 'Customer support for your Rails app: tickets that are conversations, worked
|
|
264
|
+
by your team and by AI agents with real guardrails'
|
|
240
265
|
test_files: []
|