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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f2f87e89db33072e4ceb141ec472b71ce09f22d3a383da595f195f3f441b9ee
|
|
4
|
+
data.tar.gz: 69aeb6d7e6f945f0fb517c8bfd37a6d91e749137d634d5578596a462579044b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
16
|
-
POSTED_DIR
|
|
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.
|
|
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,
|
|
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
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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:,
|
|
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:
|
|
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,
|
|
560
|
-
|
|
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
|
-
|
|
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:
|
|
905
|
+
prompt_file: prompt_file, resume: actual_resume)
|
|
858
906
|
|
|
859
|
-
resume_note =
|
|
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
|
|
1083
|
+
if exit_status.zero?
|
|
989
1084
|
LOG.info "[Discord:#{agent_name}] [fresh] Memory refresh completed successfully" if defined?(LOG)
|
|
990
|
-
|
|
991
|
-
LOG.warn "[Discord:#{agent_name}] [fresh] Memory refresh failed (exit: #{exit_status}), proceeding anyway"
|
|
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
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
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.
|
|
52
|
-
7.
|
|
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'
|
|
@@ -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
|
-
|
|
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
|