brainiac-discord 0.0.10 → 0.0.13

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 753751521918bfaf9dbbd4481272289b5e56109c8a6607a4f9bee5bb6d4158fe
4
- data.tar.gz: 143f91e2f62daec56217695b50c6d11f7b22b8d02996600ff2bc04059dacfd92
3
+ metadata.gz: 1f2f87e89db33072e4ceb141ec472b71ce09f22d3a383da595f195f3f441b9ee
4
+ data.tar.gz: 69aeb6d7e6f945f0fb517c8bfd37a6d91e749137d634d5578596a462579044b2
5
5
  SHA512:
6
- metadata.gz: 199e2440232277e9068b31a59bd407dc5b6e3c736e313da1eed4b4ad64f0ebb5b6f7fce1edc7f3e928560534b7167ae64d945683432c792d83c99e6dbdabc9d2
7
- data.tar.gz: 7625990b00a2b3e1629d11a7eed4ae5947e662cd28d1514dd65bcc1ad689eedf7e9c42020c7e6a884ee35594a2aa229207d144302e51f4ff4d8a6300665eaf84
6
+ metadata.gz: 2eab7e192cfe422009193273e88bbf8aab1b8ccecdf3d33d7643847b4799a55f44bb5c3d5f18c56eea753982491c73426942e420a1d99c418867829c9bb65fdf
7
+ data.tar.gz: 6f66c81ea46ac32423fb10a542730c3e2c71d6d7db71b959ef188b93effa68a12932f4e8e52cc1d203473882355d78339058320dbba9eb61eb8e88d62a479700
@@ -12,8 +12,9 @@ module Brainiac
12
12
  # A poller thread recovers orphaned drafts (e.g. after a server restart).
13
13
  module Delivery
14
14
  BRAINIAC_DIR_PATH = ENV.fetch("BRAINIAC_DIR", File.join(Dir.home, ".brainiac"))
15
- DRAFT_DIR = File.join(BRAINIAC_DIR_PATH, "tmp", "discord", "draft")
16
- POSTED_DIR = File.join(BRAINIAC_DIR_PATH, "tmp", "discord", "posted")
15
+ DRAFT_DIR = File.join(BRAINIAC_DIR_PATH, "tmp", "discord", "draft")
16
+ POSTED_DIR = File.join(BRAINIAC_DIR_PATH, "tmp", "discord", "posted")
17
+ PENDING_DIR = File.join(BRAINIAC_DIR_PATH, "tmp", "discord", "pending")
17
18
 
18
19
  POLLER_INTERVAL = 5 # seconds
19
20
  DRAFT_MIN_AGE = 30 # seconds — don't race the monitoring thread
@@ -30,6 +31,7 @@ module Brainiac
30
31
  def ensure_dirs!
31
32
  FileUtils.mkdir_p(DRAFT_DIR)
32
33
  FileUtils.mkdir_p(POSTED_DIR)
34
+ FileUtils.mkdir_p(PENDING_DIR)
33
35
  end
34
36
 
35
37
  def start_poller!
@@ -254,7 +254,7 @@ module Brainiac
254
254
  # Also check user_mappings for remote agent bots not connected locally
255
255
  agent_keys = []
256
256
  Gateway.each_bot { |key, _| agent_keys << key }
257
- Config.user_mappings.each do |name, _discord_id|
257
+ Config.user_mappings.each_key do |name|
258
258
  normalized = name.downcase.gsub(/[^a-z0-9]/, "-")
259
259
  next if normalized == agent_key
260
260
  next if agent_keys.include?(normalized)
@@ -389,9 +389,7 @@ module Brainiac
389
389
  end
390
390
 
391
391
  def route_dispatch(agent_key:, agent_name:, bot_token:, is_bot:, channel_id:, message_id:, message:,
392
- clean_content:, clean_content_for_prompt:, chat_mode:, fresh: false, is_thread:, is_dm:,
393
- channel_info:, parent_channel_id:, discord_user:, reply_context:,
394
- channel_history:, project_key:, project_config:, attachment_paths:,
392
+ clean_content:, clean_content_for_prompt:, chat_mode:, is_thread:, is_dm:, channel_info:, parent_channel_id:, discord_user:, reply_context:, channel_history:, project_key:, project_config:, attachment_paths:, fresh: false,
395
393
  directly_addressed: false)
396
394
  session_key = "discord-#{agent_key}-#{channel_id}-#{message_id}"
397
395
  supersede_key = "discord-#{agent_key}-#{channel_id}"
@@ -401,14 +399,24 @@ module Brainiac
401
399
  end
402
400
  handle_supersede(is_bot, supersede_key, discord_user, agent_name, bot_token)
403
401
 
404
- unless directly_addressed
405
- if intent_skip?(clean_content, agent_name: agent_name, source: :discord,
406
- channel: "Discord #{is_thread ? "thread" : "channel"}", context: channel_history)
407
- LOG.info "[Discord:#{agent_name}] Intent skip — not dispatching for: #{clean_content[0..80]}" if defined?(LOG)
402
+ # If an agent is still running (in supersede window), queue the message
403
+ # for mid-session context injection instead of spawning a second agent.
404
+ unless is_bot
405
+ active = find_supersedable_session(supersede_key)
406
+ if active
407
+ queue_pending_message(supersede_key, discord_user, clean_content, attachment_paths)
408
+ Thread.new { Api.add_reaction(channel_id, message_id, "📎", token: bot_token) }
409
+ LOG.info "[Discord:#{agent_name}] Queued follow-up from #{discord_user} for active session #{active[:session_key]}" if defined?(LOG)
408
410
  return
409
411
  end
410
412
  end
411
413
 
414
+ if !directly_addressed && intent_skip?(clean_content, agent_name: agent_name, source: :discord,
415
+ channel: "Discord #{is_thread ? "thread" : "channel"}", context: channel_history)
416
+ LOG.info "[Discord:#{agent_name}] Intent skip — not dispatching for: #{clean_content[0..80]}" if defined?(LOG)
417
+ return
418
+ end
419
+
412
420
  Thread.new do
413
421
  Api.remove_reaction(channel_id, message_id, "🛑", token: bot_token)
414
422
  Api.add_reaction(channel_id, message_id, "👀", token: bot_token)
@@ -459,6 +467,37 @@ module Brainiac
459
467
  (prev[:draft_files] || []).each { |f| FileUtils.rm_f(f) }
460
468
  end
461
469
 
470
+ def queue_pending_message(supersede_key, discord_user, content, attachment_paths)
471
+ FileUtils.mkdir_p(Delivery::PENDING_DIR)
472
+ safe_key = supersede_key.gsub(/[^a-zA-Z0-9-]/, "_")
473
+ timestamp = Time.now.strftime("%Y%m%d-%H%M%S-%L")
474
+ pending_file = File.join(Delivery::PENDING_DIR, "#{safe_key}-#{timestamp}.json")
475
+ payload = {
476
+ user: discord_user,
477
+ content: content,
478
+ timestamp: Time.now.iso8601,
479
+ attachments: attachment_paths || []
480
+ }
481
+ File.write(pending_file, JSON.pretty_generate(payload))
482
+ end
483
+
484
+ def pending_messages_for(supersede_key)
485
+ safe_key = supersede_key.gsub(/[^a-zA-Z0-9-]/, "_")
486
+ pattern = File.join(Delivery::PENDING_DIR, "#{safe_key}-*.json")
487
+ files = Dir.glob(pattern)
488
+ messages = files.filter_map do |f|
489
+ JSON.parse(File.read(f))
490
+ rescue JSON::ParserError
491
+ nil
492
+ end
493
+ [messages, files]
494
+ end
495
+
496
+ def clear_pending_messages(supersede_key)
497
+ _, files = pending_messages_for(supersede_key)
498
+ files.each { |f| FileUtils.rm_f(f) }
499
+ end
500
+
462
501
  def build_project_context(project_key, project_config, agent_name)
463
502
  if project_config
464
503
  repo_path = project_config["repo_path"]
@@ -484,10 +523,7 @@ module Brainiac
484
523
  end
485
524
 
486
525
  def dispatch_session(agent_key:, agent_name:, bot_token:, channel_id:, message_id:, message:,
487
- clean_content:, clean_content_for_prompt:, chat_mode:, fresh: false, is_thread:, is_dm:,
488
- channel_info:, parent_channel_id:, discord_user:, reply_context:,
489
- channel_history:, project_key:, project_config:, project_context:,
490
- session_key:, supersede_key:, attachment_paths:, is_bot:)
526
+ clean_content:, clean_content_for_prompt:, chat_mode:, is_thread:, is_dm:, channel_info:, parent_channel_id:, discord_user:, reply_context:, channel_history:, project_key:, project_config:, project_context:, session_key:, supersede_key:, attachment_paths:, is_bot:, fresh: false)
491
527
  timestamp = Time.now.strftime("%Y%m%d-%H%M%S")
492
528
  response_dir = File.join(Delivery::BRAINIAC_DIR_PATH, "tmp")
493
529
  response_basename = "discord-response-#{timestamp}-#{agent_key}-#{message_id}"
@@ -522,24 +558,34 @@ module Brainiac
522
558
  )
523
559
  end
524
560
 
561
+ # Resolve CLI overrides and work_dir BEFORE building the prompt so we can determine
562
+ # whether resume will actually work. This prevents sending a lean prompt when
563
+ # the CLI won't actually have session state.
564
+ work_dir = chat_mode_fallback(agent_key, agent_name, message_id, chat_mode, thread_worktree_path) ||
565
+ (project_config ? project_config["repo_path"] : Dir.pwd)
566
+ model, effort, cli_provider_override = resolve_overrides(
567
+ clean_content, project_config, thread_model, thread_effort, thread_cli_provider
568
+ )
569
+ resolved = resolve_project_cli_config(project_config || DEFAULT_PROJECT,
570
+ cli_provider_override: cli_provider_override, agent_name: agent_name)
571
+
572
+ # Convert should_resume to the actual flag/symbol. If resolve_resume returns false,
573
+ # we know the CLI won't actually resume, so we shouldn't send a lean prompt.
574
+ actual_resume = should_resume ? resolve_resume(true, resolved, work_dir) : false
575
+ use_lean_prompt = actual_resume && thread_worktree_path
576
+
525
577
  prompt = build_prompt(
526
- should_resume: should_resume, thread_worktree_path: thread_worktree_path,
578
+ should_resume: use_lean_prompt, thread_worktree_path: thread_worktree_path,
527
579
  clean_content_for_prompt: clean_content_for_prompt,
528
580
  discord_user: discord_user, channel_name: channel_info&.dig("name") || channel_id, reply_context: reply_context,
529
581
  channel_history: channel_history, thread_root_context: thread_root_context,
530
582
  project_context: project_context, response_file: response_file, card_id: card_id,
531
- brain_context: brain_context, agent_name: agent_name
583
+ brain_context: brain_context, agent_name: agent_name, supersede_key: supersede_key
532
584
  )
533
585
 
534
- work_dir = chat_mode_fallback(agent_key, agent_name, message_id, chat_mode, thread_worktree_path) ||
535
- (project_config ? project_config["repo_path"] : Dir.pwd)
536
586
  prompt_file = File.join(response_dir, "discord-prompt-#{timestamp}-#{agent_key}-#{message_id}.md")
537
587
  File.write(prompt_file, prompt)
538
588
 
539
- model, effort, cli_provider_override = resolve_overrides(
540
- clean_content, project_config, thread_model, thread_effort, thread_cli_provider
541
- )
542
-
543
589
  persist_overrides(thread_map_key, clean_content, project_config,
544
590
  cli_provider: cli_provider_override, model: model, effort: effort,
545
591
  prev_cli_provider: thread_cli_provider, prev_model: thread_model, prev_effort: thread_effort)
@@ -556,8 +602,8 @@ module Brainiac
556
602
  agent_key: agent_key, agent_name: agent_name, bot_token: bot_token,
557
603
  channel_id: channel_id, message_id: message_id, discord_user: discord_user,
558
604
  work_dir: work_dir, prompt_file: prompt_file, response_file: response_file,
559
- meta_file: meta_file, model: model, effort: effort, should_resume: should_resume,
560
- cli_provider_override: cli_provider_override, project_config: project_config,
605
+ meta_file: meta_file, model: model, effort: effort, actual_resume: actual_resume,
606
+ resolved: resolved, project_config: project_config,
561
607
  session_key: session_key, supersede_key: supersede_key,
562
608
  attachment_paths: attachment_paths, timestamp: timestamp, response_dir: response_dir
563
609
  )
@@ -823,7 +869,7 @@ module Brainiac
823
869
 
824
870
  def build_prompt(should_resume:, thread_worktree_path:, clean_content_for_prompt:,
825
871
  discord_user:, channel_name:, reply_context:, channel_history:, thread_root_context:,
826
- project_context:, response_file:, card_id:, brain_context:, agent_name:)
872
+ project_context:, response_file:, card_id:, brain_context:, agent_name:, supersede_key: nil)
827
873
  if should_resume && thread_worktree_path
828
874
  return Brainiac::Plugins::Discord::Prompts.render_resume(
829
875
  message_body: clean_content_for_prompt, discord_user: discord_user,
@@ -831,12 +877,16 @@ module Brainiac
831
877
  )
832
878
  end
833
879
 
880
+ safe_key = supersede_key&.gsub(/[^a-zA-Z0-9-]/, "_") || "unknown"
881
+ pending_glob = File.join(Delivery::PENDING_DIR, "#{safe_key}-*.json")
882
+
834
883
  template_vars = {
835
884
  "DISCORD_USER" => discord_user, "CHANNEL_NAME" => channel_name,
836
885
  "MESSAGE_BODY" => clean_content_for_prompt, "REPLY_CONTEXT" => reply_context,
837
886
  "CHANNEL_HISTORY" => channel_history, "THREAD_ROOT_CONTEXT" => thread_root_context,
838
887
  "PROJECT_CONTEXT" => project_context, "RESPONSE_FILE" => response_file,
839
- "COMMENT_CREATOR" => discord_user, "DISCORD_MENTION_ROSTER" => Api.mention_roster
888
+ "COMMENT_CREATOR" => discord_user, "DISCORD_MENTION_ROSTER" => Api.mention_roster,
889
+ "PENDING_MESSAGES_GLOB" => pending_glob
840
890
  }
841
891
 
842
892
  template_vars["CARD_ID"] = card_id
@@ -846,17 +896,15 @@ module Brainiac
846
896
 
847
897
  def spawn_agent(agent_key:, agent_name:, bot_token:, channel_id:, message_id:, discord_user:,
848
898
  work_dir:, prompt_file:, response_file:, meta_file:, model:, effort:,
849
- should_resume:, cli_provider_override:, project_config:,
899
+ actual_resume:, resolved:, project_config:,
850
900
  session_key:, supersede_key:, attachment_paths:, timestamp:, response_dir:)
851
901
  agent_config_name = agent_key.downcase.gsub(/[^a-z0-9-]/, "-")
852
902
  log_file = File.join(response_dir, "discord-agent-#{timestamp}-#{agent_key}-#{message_id}.log")
853
903
 
854
- resolved = resolve_project_cli_config(project_config || DEFAULT_PROJECT,
855
- cli_provider_override: cli_provider_override, agent_name: agent_name)
856
904
  cmd = build_agent_cmd(resolved, agent_config_name: agent_config_name, model: model, effort: effort,
857
- prompt_file: prompt_file, resume: should_resume)
905
+ prompt_file: prompt_file, resume: actual_resume)
858
906
 
859
- resume_note = should_resume ? ", resuming" : ""
907
+ resume_note = actual_resume ? ", resuming" : ""
860
908
  if defined?(LOG)
861
909
  LOG.info "[Discord:#{agent_name}] Dispatching for #{discord_user} " \
862
910
  "(model: #{model || "default"}, effort: #{effort || "default"}, cli: #{resolved["agent_cli"]}#{resume_note}), tail -f #{log_file}"
@@ -870,6 +918,17 @@ module Brainiac
870
918
  LOG.info "[Discord:#{agent_name}] Injecting #{agent_env.size} env var(s): #{agent_env.keys.join(", ")}" if defined?(LOG)
871
919
  end
872
920
 
921
+ # Inject a GitHub App token so `gh`/`git` run as the agent's own bot
922
+ # identity (e.g. galen[bot]) instead of the host machine's personal
923
+ # `gh auth`. Without this, `gh pr create` in the prompt would open PRs
924
+ # under whoever is logged in on the box. No-op if the github plugin
925
+ # isn't loaded or the agent has no app configured.
926
+ gh_env = github_agent_token_env(agent_name, project_config)
927
+ unless gh_env.empty?
928
+ spawn_env.merge!(gh_env)
929
+ LOG.info "[Discord:#{agent_name}] Injecting GitHub App token (GH_TOKEN) for bot identity" if defined?(LOG)
930
+ end
931
+
873
932
  head_before, status_before = capture_brainiac_state(project_config, work_dir)
874
933
  prompt_mode = resolved["prompt_mode"] || "stdin"
875
934
 
@@ -891,10 +950,46 @@ module Brainiac
891
950
  message_id: message_id, bot_token: bot_token, response_file: response_file,
892
951
  meta_file: meta_file, prompt_file: prompt_file, log_file: log_file,
893
952
  attachment_paths: attachment_paths, project_config: project_config,
894
- head_before: head_before, status_before: status_before
953
+ head_before: head_before, status_before: status_before,
954
+ supersede_key: supersede_key
895
955
  )
896
956
  end
897
957
 
958
+ # Build a { "GH_TOKEN" => ... } env hash so the spawned agent's `gh`
959
+ # and `git` invocations authenticate as the agent's own GitHub App bot
960
+ # rather than the host's personal `gh auth`. This is what makes the
961
+ # prompt's `gh pr create` open PRs under e.g. galen[bot].
962
+ #
963
+ # Reuses the brainiac-github plugin's AppClient. The github plugin is
964
+ # optional, so everything is guarded: if it isn't loaded, or no app is
965
+ # configured for the agent (or the shared "brainiac" app), we return an
966
+ # empty hash and `gh` falls back to whatever's authenticated on the box.
967
+ def github_agent_token_env(agent_name, project_config)
968
+ return {} unless agent_name && project_config
969
+
970
+ repo = project_config["github_repo"]
971
+ return {} unless repo && !repo.to_s.empty?
972
+
973
+ return {} unless defined?(Brainiac::Plugins::Github::AppClient)
974
+
975
+ client = Brainiac::Plugins::Github::AppClient
976
+ effective_agent = if client.configured?(agent_name)
977
+ agent_name
978
+ elsif client.configured?("brainiac")
979
+ "brainiac"
980
+ end
981
+ return {} unless effective_agent
982
+
983
+ repo_owner = repo.to_s.split("/").first
984
+ token = client.installation_token_for(effective_agent, repo_owner: repo_owner)
985
+ return {} unless token && !token.empty?
986
+
987
+ { "GH_TOKEN" => token }
988
+ rescue StandardError => e
989
+ LOG.warn "[Discord:#{agent_name}] Could not mint GitHub App token: #{e.message}" if defined?(LOG)
990
+ {}
991
+ end
992
+
898
993
  def capture_brainiac_state(project_config, work_dir)
899
994
  return [nil, nil] unless project_config
900
995
 
@@ -985,10 +1080,10 @@ module Brainiac
985
1080
  Process.wait(pid)
986
1081
  exit_status = $CHILD_STATUS.exitstatus
987
1082
 
988
- if exit_status == 0
1083
+ if exit_status.zero?
989
1084
  LOG.info "[Discord:#{agent_name}] [fresh] Memory refresh completed successfully" if defined?(LOG)
990
- else
991
- LOG.warn "[Discord:#{agent_name}] [fresh] Memory refresh failed (exit: #{exit_status}), proceeding anyway" if defined?(LOG)
1085
+ elsif defined?(LOG)
1086
+ LOG.warn "[Discord:#{agent_name}] [fresh] Memory refresh failed (exit: #{exit_status}), proceeding anyway"
992
1087
  end
993
1088
 
994
1089
  Api.remove_reaction(channel_id, message_id, "📖", token: bot_token)
@@ -1034,11 +1129,14 @@ module Brainiac
1034
1129
 
1035
1130
  def monitor_agent(pid:, session_key:, agent_name:, agent_config_name:, channel_id:, message_id:,
1036
1131
  bot_token:, response_file:, meta_file:, prompt_file:, log_file:,
1037
- attachment_paths:, project_config:, head_before:, status_before:)
1132
+ attachment_paths:, project_config:, head_before:, status_before:, supersede_key: nil)
1038
1133
  Thread.new do
1039
1134
  Process.wait(pid)
1040
1135
  exit_status = $CHILD_STATUS
1041
1136
 
1137
+ # Clean up any pending messages that the agent may not have consumed
1138
+ clear_pending_messages(supersede_key) if supersede_key
1139
+
1042
1140
  session_cancelled = ACTIVE_SESSIONS_MUTEX.synchronize { !ACTIVE_SESSIONS.key?(session_key) }
1043
1141
 
1044
1142
  if exit_status.signaled? || session_cancelled
@@ -1172,12 +1270,13 @@ module Brainiac
1172
1270
  # Detect transient CLI errors that don't warrant a full crash notification.
1173
1271
  # These are random upstream failures (model timeouts, tool approval glitches)
1174
1272
  # that resolve on retry — reacting with an emoji is sufficient.
1175
- TRANSIENT_CLI_ERROR_PATTERN = /
1176
- Failed\sto\sreceive\sthe\snext\smessage|
1177
- Kiro\sfailed\sto\sgenerate\sa\sresponse|
1178
- Tool\sapproval\srequired\sbut\s--no-interactive|
1179
- Kiro\sis\shaving\strouble\sresponding
1180
- /ix
1273
+ TRANSIENT_CLI_ERROR_PATTERN = # rubocop:disable Lint/UselessConstantScoping
1274
+ /
1275
+ Failed\sto\sreceive\sthe\snext\smessage|
1276
+ Kiro\sfailed\sto\sgenerate\sa\sresponse|
1277
+ Tool\sapproval\srequired\sbut\s--no-interactive|
1278
+ Kiro\sis\shaving\strouble\sresponding
1279
+ /ix # rubocop:enable Lint/UselessConstantScoping
1181
1280
 
1182
1281
  def transient_cli_error?(log_file)
1183
1282
  return false unless log_file && File.exist?(log_file)
@@ -48,8 +48,8 @@ module Brainiac
48
48
  3. Make the changes, test if applicable
49
49
  4. Commit with a clear message
50
50
  5. Push the branch
51
- 6. Summarize what you did in your response file
52
- 7. If it's substantial or needs review, mention opening a PR (but don't create it unless asked)
51
+ 6. Open a PR with `gh pr create --fill` (or with a descriptive body)
52
+ 7. Summarize what you did in your response file, including a link to the PR
53
53
 
54
54
  **When responding conversationally:**
55
55
  - Answer questions about the codebase, architecture, conventions
@@ -84,6 +84,19 @@ module Brainiac
84
84
  - The current topic/focus as of this session
85
85
  This is the ONLY way future sessions will know what happened in the middle of the conversation.
86
86
 
87
+ ### Pending Follow-Up Messages (check BEFORE writing your response)
88
+ While you're working, the user may send additional messages that get queued for you.
89
+ Before writing your response file, check for pending messages:
90
+ ```bash
91
+ ls {{PENDING_MESSAGES_GLOB}} 2>/dev/null
92
+ ```
93
+ If any files exist, read them — they contain JSON with `user`, `content`, and `timestamp`.
94
+ Incorporate the follow-up context into your response. Address anything new the user said.
95
+ After reading them, delete the pending files:
96
+ ```bash
97
+ rm -f {{PENDING_MESSAGES_GLOB}}
98
+ ```
99
+
87
100
  PROMPT
88
101
 
89
102
  SITUATION = <<~'PROMPT'
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Discord
6
- VERSION = "0.0.10"
6
+ VERSION = "0.0.13"
7
7
  end
8
8
  end
9
9
  end
@@ -89,7 +89,8 @@ module Brainiac
89
89
 
90
90
  # Handle forum posts
91
91
  if ctx[:forum_title] && Api.forum_channel?(target, token: token)
92
- Api.create_forum_post(target, title: ctx[:forum_title], content: message, token: token)
92
+ title = "#{ctx[:forum_title]} #{Time.now.strftime("%b %d, %Y")}"
93
+ Api.create_forum_post(target, title: title, content: message, token: token)
93
94
  elsif ctx[:forum_reply_to_latest] && Api.forum_channel?(target, token: token)
94
95
  latest = Api.find_latest_forum_thread(target, token: token)
95
96
  if latest
@@ -97,6 +98,9 @@ module Brainiac
97
98
  else
98
99
  Api.send_long_message(target, message, token: token)
99
100
  end
101
+ elsif Api.forum_channel?(target, token: token)
102
+ title = "#{agent || "Notification"} — #{Time.now.strftime("%b %d, %Y")}"
103
+ Api.create_forum_post(target, title: title, content: message, token: token)
100
104
  else
101
105
  Api.send_long_message(target, message, token: token)
102
106
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainiac-discord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis