Class: RailsConsoleAi::Channel::Slack

Inherits:
Base
  • Object
show all
Defined in:
lib/rails_console_ai/channel/slack.rb

Constant Summary collapse

ANSI_REGEX =
/\e\[[0-9;]*m/

Instance Method Summary collapse

Methods inherited from Base

#edit_code

Constructor Details

#initialize(slack_bot:, channel_id:, thread_ts:, user_name: nil) ⇒ Slack

Returns a new instance of Slack.



8
9
10
11
12
13
14
15
16
17
# File 'lib/rails_console_ai/channel/slack.rb', line 8

def initialize(slack_bot:, channel_id:, thread_ts:, user_name: nil)
  @slack_bot = slack_bot
  @channel_id = channel_id
  @thread_ts = thread_ts
  @user_name = user_name
  @reply_queue = Queue.new
  @cancelled = false
  @log_prefix = "[#{@channel_id}/#{@thread_ts}] @#{@user_name}"
  @output_log = StringIO.new
end

Instance Method Details

#cancel!Object



19
20
21
# File 'lib/rails_console_ai/channel/slack.rb', line 19

def cancel!
  @cancelled = true
end

#cancelled?Boolean

Returns:

  • (Boolean)


23
24
25
# File 'lib/rails_console_ai/channel/slack.rb', line 23

def cancelled?
  @cancelled
end

#confirm(_text) ⇒ Object



88
89
90
# File 'lib/rails_console_ai/channel/slack.rb', line 88

def confirm(_text)
  'y'
end

#console_capture_stringObject



159
160
161
# File 'lib/rails_console_ai/channel/slack.rb', line 159

def console_capture_string
  @output_log.string
end

#display(text) ⇒ Object



27
28
29
# File 'lib/rails_console_ai/channel/slack.rb', line 27

def display(text)
  post(strip_ansi(text))
end

#display_code(code) ⇒ Object



64
65
66
67
68
69
# File 'lib/rails_console_ai/channel/slack.rb', line 64

def display_code(code)
  # Don't post raw code/plan steps to Slack — non-technical users don't need to see Ruby
  # But do log to STDOUT so server logs show what was generated/executed
  @output_log.write("# Generated code:\n#{code}\n")
  STDOUT.puts "#{@log_prefix} (code)\n# Generated code:\n#{code}"
end

#display_dim(text) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/rails_console_ai/channel/slack.rb', line 31

def display_dim(text)
  raw = strip_ansi(text)
  stripped = raw.strip

  if stripped =~ /\AThinking\.\.\.|\AAttempting to fix|\ACancelled|\A_session:/
    post(stripped)
  elsif stripped =~ /\ACalling LLM/
    # Technical LLM round status — suppress in Slack
    @output_log.write("#{stripped}\n")
    STDOUT.puts "#{@log_prefix} (dim) #{stripped}"
  elsif raw =~ /\A {2,4}\S/ && stripped.length > 10
    # LLM thinking text (2-space indent from conversation engine) — show as status
    post(stripped)
  else
    # Tool result previews (5+ space indent) and other technical noise — log only
    @output_log.write("#{stripped}\n")
    STDOUT.puts "#{@log_prefix} (dim) #{stripped}"
  end
end

#display_error(text) ⇒ Object



55
56
57
# File 'lib/rails_console_ai/channel/slack.rb', line 55

def display_error(text)
  post(":x: #{strip_ansi(text)}")
end

#display_result(_result) ⇒ Object



78
79
80
81
# File 'lib/rails_console_ai/channel/slack.rb', line 78

def display_result(_result)
  # Don't post raw return values to Slack — the LLM formats output via puts
  nil
end

#display_result_output(output) ⇒ Object



71
72
73
74
75
76
# File 'lib/rails_console_ai/channel/slack.rb', line 71

def display_result_output(output)
  text = strip_ansi(output).strip
  return if text.empty?
  text = text[0, 3000] + "\n... (truncated)" if text.length > 3000
  post("```#{text}```")
end

#display_tool_call(text) ⇒ Object



59
60
61
62
# File 'lib/rails_console_ai/channel/slack.rb', line 59

def display_tool_call(text)
  @output_log.write("-> #{text}\n")
  STDOUT.puts "#{@log_prefix} -> #{text}"
end

#display_warning(text) ⇒ Object



51
52
53
# File 'lib/rails_console_ai/channel/slack.rb', line 51

def display_warning(text)
  post(":warning: #{strip_ansi(text)}")
end

#log_input(text) ⇒ Object



149
150
151
# File 'lib/rails_console_ai/channel/slack.rb', line 149

def log_input(text)
  @output_log.write("@#{@user_name}: #{text}\n")
end

#modeObject



96
97
98
# File 'lib/rails_console_ai/channel/slack.rb', line 96

def mode
  'slack'
end

#prompt(text) ⇒ Object



83
84
85
86
# File 'lib/rails_console_ai/channel/slack.rb', line 83

def prompt(text)
  post(strip_ansi(text))
  @reply_queue.pop
end

#receive_reply(text) ⇒ Object

Called by SlackBot when a thread reply arrives



154
155
156
157
# File 'lib/rails_console_ai/channel/slack.rb', line 154

def receive_reply(text)
  @output_log.write("@#{@user_name}: #{text}\n")
  @reply_queue.push(text)
end

#supports_danger?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/rails_console_ai/channel/slack.rb', line 100

def supports_danger?
  false
end

#supports_editing?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/rails_console_ai/channel/slack.rb', line 104

def supports_editing?
  false
end

#system_instructionsObject



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/rails_console_ai/channel/slack.rb', line 112

def system_instructions
  <<~INSTRUCTIONS.strip
    ## Response Formatting (Slack Channel)

    You are responding to non-technical users in Slack. Follow these rules:

    ## Code Execution
    - ALWAYS use the `execute_code` tool to run Ruby code. Do NOT put code in markdown
      code fences expecting it to be executed — code fences are display-only in Slack.
    - Use `execute_code` for simple queries, and `execute_plan` for multi-step operations.
    - If the user asks you to provide code they can run later, put it in a code fence
      in your text response (it will be displayed but not executed).

    ## Formatting
    - Slack does NOT support markdown tables. For tabular data, use `puts` to print
      a plain-text table inside a code block. Use fixed-width columns with padding so
      columns align. Example format:
      ```
      ID   Name              Email
      123  John Smith        john@example.com
      456  Jane Doe          jane@example.com
      ```
    - Use `puts` with formatted output instead of returning arrays or hashes
    - Summarize findings in plain, simple language
    - Do NOT show technical details like SQL queries, token counts, or class names
    - Keep explanations simple and jargon-free
    - Never return raw Ruby objects — always present data in a human-readable way
    - The output of `puts` in your code is automatically shown to the user. Do NOT
      repeat or re-display data that your code already printed via `puts`.
      Just add a brief summary after (e.g. "10 events found" or "Let me know if you need more detail").
    - Do not offer to make changes or take actions on behalf of the user. Only report findings.
    - This is a live production database — other processes, users, and background jobs are
      constantly changing data. Never assume results will be the same as a previous query.
      Always re-run queries when asked, even if you just ran the same one.
  INSTRUCTIONS
end

#user_identityObject



92
93
94
# File 'lib/rails_console_ai/channel/slack.rb', line 92

def user_identity
  @user_name
end

#wrap_llm_call(&block) ⇒ Object



108
109
110
# File 'lib/rails_console_ai/channel/slack.rb', line 108

def wrap_llm_call(&block)
  yield
end