brainiac-discord 0.0.20 → 0.0.22

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: 173d9471b14272a611b844fb7a12e0d728f812ab2b06cda08b05faf26329243c
4
- data.tar.gz: c0658fe873c620e9b1be027d1866035b4d6bd846e13088626eefa174e77c186d
3
+ metadata.gz: 0c4e465a488b1eb8ff21dc46d539271cd85a35086fbed35a32e49bad609ee949
4
+ data.tar.gz: 5095805d24f0422c6ad9fe0ea7bb363965732b6d2b6df047d00295ce14b05194
5
5
  SHA512:
6
- metadata.gz: d3997e24550bd3be11cb48942dafa1b624cdc0288410ce5388e4eca6a5145f61291d7dce56295c34544c9dad46407572c3b587e96b3c35a2c168ea4a34afa09e
7
- data.tar.gz: 55a5b86aae6ce19a9d37b2e2e2d438d3c7ddc80c166367996ffaa9f1355a98613e2f10cb0582c4e171e8e135b27b507cee5317731173a8e01bec63ff795de870
6
+ metadata.gz: e8c98fba862167e6c12096161dfb5fd964b12c6cb938c767279b00c2002a0770993c0f3a68b6b9f35fd391fbecba83d19f6fb21b1d1972a05aad4adcd89c78ff
7
+ data.tar.gz: 37d9072132b6c961f15c7237f9b5bae98db6ddf0933f39ea9bfee4c9b983a85eb8b007e6cf2b11d8fc19fe75c261f733429cbad1827d124c4c47cbdaa811147e
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "websocket-client-simple"
4
4
  require "websocket"
5
+ require "time"
5
6
 
6
7
  module Brainiac
7
8
  module Plugins
@@ -23,6 +24,12 @@ module Brainiac
23
24
  BACKOFF_HANDSHAKE_BASE = 30
24
25
  BACKOFF_CAP = 300
25
26
 
27
+ # How long after a message is posted an edit still counts as a real,
28
+ # conversational edit worth re-dispatching on. Edits that land outside
29
+ # this window are treated as server-side embed refreshes (Discord
30
+ # restamping edited_timestamp when it unfurls a link), not human intent.
31
+ EDIT_FRESHNESS_WINDOW = 300 # seconds (5 minutes)
32
+
26
33
  # Per-bot state: { agent_key => { token:, user_id:, status:, thread: } }
27
34
  @bots = {}
28
35
  @bots_mutex = Mutex.new
@@ -376,7 +383,16 @@ module Brainiac
376
383
  LOG.error "[Discord:#{agent_display}] Error handling message: #{e.message}\n#{e.backtrace.first(3).join("\n")}" if defined?(LOG)
377
384
  end
378
385
  when "MESSAGE_UPDATE"
379
- if data["edited_timestamp"]
386
+ # Discord emits MESSAGE_UPDATE for genuine human edits AND for
387
+ # server-side embed (un)furls on existing messages — e.g. lazily
388
+ # generating a link preview days after the message was posted. Both
389
+ # stamp edited_timestamp, so re-dispatching on every update means a
390
+ # bare URL in an old agent-mentioning message can silently wake the
391
+ # agent long after the fact. Only treat an update as actionable when
392
+ # the edit happened close to the message's own creation time, which
393
+ # is what a real conversational edit (typo fix, afterthought) looks
394
+ # like. See message.rb for the shared dispatch path.
395
+ if data["edited_timestamp"] && !stale_edit?(data["timestamp"], data["edited_timestamp"])
380
396
  Thread.new do
381
397
  Message.handle(data, agent_key, bot_token, bot_user_id)
382
398
  rescue StandardError => e
@@ -384,6 +400,9 @@ module Brainiac
384
400
  LOG.error "[Discord:#{agent_display}] Error handling message update: #{e.message}\n#{e.backtrace.first(3).join("\n")}"
385
401
  end
386
402
  end
403
+ elsif data["edited_timestamp"] && defined?(LOG)
404
+ LOG.info "[Discord:#{agent_display}] Ignoring stale MESSAGE_UPDATE " \
405
+ "(likely embed refresh) on message #{data["id"]}"
387
406
  end
388
407
  when "MESSAGE_REACTION_ADD"
389
408
  Thread.new do
@@ -416,6 +435,25 @@ module Brainiac
416
435
  bot_user_id
417
436
  end
418
437
 
438
+ # True when a MESSAGE_UPDATE's edit happened long enough after the
439
+ # message was created that it's almost certainly a server-side embed
440
+ # refresh rather than a human edit. Guards against spurious agent
441
+ # dispatches from Discord lazily unfurling link previews on old
442
+ # messages that mention the bot.
443
+ #
444
+ # Fails open (returns false → treat as a real edit) when either
445
+ # timestamp is missing or unparseable, so a parsing hiccup never
446
+ # silently swallows a genuine edit.
447
+ def stale_edit?(created_at, edited_at)
448
+ return false if created_at.nil? || edited_at.nil?
449
+
450
+ created = Time.iso8601(created_at.to_s)
451
+ edited = Time.iso8601(edited_at.to_s)
452
+ (edited - created) > EDIT_FRESHNESS_WINDOW
453
+ rescue ArgumentError
454
+ false
455
+ end
456
+
419
457
  def mark_bot_ready(agent_key, agent_display, bot_user_id, data)
420
458
  @bots_mutex.synchronize do
421
459
  @bots[agent_key][:user_id] = bot_user_id
@@ -714,8 +714,15 @@ module Brainiac
714
714
  end
715
715
 
716
716
  def resolve_overrides(clean_content, project_config, thread_model, thread_effort, thread_cli_provider)
717
- cli_provider = detect_cli_provider(text: clean_content) || thread_cli_provider
717
+ inline_cli = detect_cli_provider(text: clean_content)
718
+ cli_provider = inline_cli || thread_cli_provider
718
719
  has_explicit_model = explicit_model_tag?(clean_content, project_config, cli_provider_override: cli_provider)
720
+
721
+ # The stored model is a resolved ID from the previous provider's model map, so it
722
+ # means nothing to a different provider. Drop it on a switch and fall back to the
723
+ # new provider's default (unless this message also carries a model tag).
724
+ # Effort is kept: it's generic and gets clamped per provider anyway.
725
+ thread_model = nil if provider_switched?(inline_cli, thread_cli_provider)
719
726
  has_explicit_effort = clean_content.match?(/\[effort:\w+\]/i)
720
727
 
721
728
  model = if has_explicit_model
@@ -737,6 +744,10 @@ module Brainiac
737
744
  [model, effort, cli_provider]
738
745
  end
739
746
 
747
+ def provider_switched?(inline_cli, prev_cli_provider)
748
+ !inline_cli.nil? && inline_cli != prev_cli_provider
749
+ end
750
+
740
751
  # Persist inline overrides to the thread map (and work item map if a work item exists).
741
752
  # Only writes when a new inline tag was detected (differs from previously stored values).
742
753
  def persist_overrides(thread_map_key, clean_content, project_config,
@@ -761,7 +772,8 @@ module Brainiac
761
772
  entry = map[thread_map_key]
762
773
  if entry
763
774
  entry["cli_provider"] = cli_provider if inline_cli
764
- entry["model"] = model if inline_model
775
+ # Clear the old provider's model on a switch so it can't resurface later
776
+ entry["model"] = inline_model if inline_model || provider_switched?(inline_cli, prev_cli_provider)
765
777
  entry["effort"] = effort if inline_effort
766
778
  Config.save_thread_map(map)
767
779
  end
@@ -3,7 +3,7 @@
3
3
  module Brainiac
4
4
  module Plugins
5
5
  module Discord
6
- VERSION = "0.0.20"
6
+ VERSION = "0.0.22"
7
7
  end
8
8
  end
9
9
  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.20
4
+ version: 0.0.22
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Davis