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



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

def confirm(_text)
  'y'
end

#console_capture_stringObject



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

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



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

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)"
  code.each_line { |line| STDOUT.puts "#{@log_prefix} (code) #{line.rstrip}" }
end

#display_error(text) ⇒ Object



53
54
55
# File 'lib/rails_console_ai/channel/slack.rb', line 53

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

#display_result(_result) ⇒ Object



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

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



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

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_status(text) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/rails_console_ai/channel/slack.rb', line 37

def display_status(text)
  stripped = strip_ansi(text).strip
  return if stripped.empty?

  if stripped =~ /\AThinking\.\.\.|\AAttempting to fix|\ACancelled|\A_session:/
    post(stripped)
  else
    @output_log.write("#{stripped}\n")
    log_prefixed("(status)", stripped)
  end
end

#display_thinking(text) ⇒ Object



31
32
33
34
35
# File 'lib/rails_console_ai/channel/slack.rb', line 31

def display_thinking(text)
  stripped = strip_ansi(text).strip
  return if stripped.empty?
  post(stripped)
end

#display_tool_call(text) ⇒ Object



57
58
59
60
# File 'lib/rails_console_ai/channel/slack.rb', line 57

def display_tool_call(text)
  @output_log.write("-> #{text}\n")
  log_prefixed("->", text)
end

#display_warning(text) ⇒ Object



49
50
51
# File 'lib/rails_console_ai/channel/slack.rb', line 49

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

#log_input(text) ⇒ Object



145
146
147
# File 'lib/rails_console_ai/channel/slack.rb', line 145

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

#modeObject



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

def mode
  'slack'
end

#prompt(text) ⇒ Object



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

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

#receive_reply(text) ⇒ Object

Called by SlackBot when a thread reply arrives



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

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

#supports_danger?Boolean

Returns:

  • (Boolean)


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

def supports_danger?
  false
end

#supports_editing?Boolean

Returns:

  • (Boolean)


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

def supports_editing?
  false
end

#system_instructionsObject



111
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
# File 'lib/rails_console_ai/channel/slack.rb', line 111

def system_instructions
  <<~INSTRUCTIONS.strip
    ## Slack Channel

    You are responding in a Slack thread.

    ## 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.
    - 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



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

def user_identity
  @user_name
end

#wrap_llm_call(&block) ⇒ Object



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

def wrap_llm_call(&block)
  yield
end