brainiac 0.0.11 → 0.0.12
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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +134 -151
- data/bin/brainiac +89 -110
- data/brainiac.gemspec +4 -3
- data/docs/waybar-config.md +7 -27
- data/lib/brainiac/agents.rb +5 -5
- data/lib/brainiac/config.rb +0 -36
- data/lib/brainiac/cron.rb +15 -17
- data/lib/brainiac/helpers.rb +58 -31
- data/lib/brainiac/hooks.rb +1 -1
- data/lib/brainiac/notifications.rb +2 -2
- data/lib/brainiac/prompts.rb +11 -133
- data/lib/brainiac/restart.rb +0 -3
- data/lib/brainiac/routes/api.rb +6 -32
- data/lib/brainiac/sessions.rb +1 -2
- data/lib/brainiac/version.rb +1 -1
- data/monitor/shared.rb +1 -1
- data/receiver.rb +2 -227
- data/templates/agents.json.example +2 -2
- data/templates/brainiac.json.example +1 -6
- data/templates/testflight.json.example +1 -1
- data/templates/users.json.example +20 -82
- metadata +5 -9
- data/lib/brainiac/handlers/github.rb +0 -390
- data/lib/brainiac/handlers/zoho.rb +0 -429
- data/lib/brainiac/zoho_mail_api.rb +0 -110
- data/templates/github.json.example +0 -4
- data/templates/zoho.json.example +0 -27
data/lib/brainiac/helpers.rb
CHANGED
|
@@ -125,15 +125,6 @@ def slugify(title, max_length: 40)
|
|
|
125
125
|
title.downcase.gsub(/[^a-z0-9\s-]/, "").strip.gsub(/\s+/, "-").slice(0, max_length).chomp("-")
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
def verify_github_signature!(request, payload_body)
|
|
129
|
-
signature = request.env["HTTP_X_HUB_SIGNATURE_256"]
|
|
130
|
-
halt 403, { error: "Missing GitHub signature" }.to_json unless signature
|
|
131
|
-
secret = github_webhook_secret
|
|
132
|
-
halt 500, { error: "GitHub webhook secret not configured" }.to_json unless secret
|
|
133
|
-
computed = "sha256=#{OpenSSL::HMAC.hexdigest("sha256", secret, payload_body)}"
|
|
134
|
-
halt 403, { error: "Invalid GitHub signature" }.to_json unless Rack::Utils.secure_compare(signature, computed)
|
|
135
|
-
end
|
|
136
|
-
|
|
137
128
|
def run_cmd(*cmd, chdir:, env: {})
|
|
138
129
|
LOG.info "Running: #{cmd.join(" ")} (in #{chdir})"
|
|
139
130
|
stdout, stderr, status = Open3.capture3(env, *cmd, chdir: chdir)
|
|
@@ -184,7 +175,6 @@ end
|
|
|
184
175
|
def notify_agent_crash(exit_status:, log_file:, agent_name:, source:, source_context:, project_config:)
|
|
185
176
|
agent_display = agent_name || "Agent"
|
|
186
177
|
snippet = extract_crash_snippet(log_file)
|
|
187
|
-
snippet_block = snippet ? "\n```\n#{snippet[-1500..]}\n```" : ""
|
|
188
178
|
|
|
189
179
|
# Emit to plugins — they handle their own channel-specific delivery
|
|
190
180
|
handled = Brainiac.emit(:agent_crashed,
|
|
@@ -192,28 +182,66 @@ def notify_agent_crash(exit_status:, log_file:, agent_name:, source:, source_con
|
|
|
192
182
|
source: source, source_context: source_context, project_config: project_config,
|
|
193
183
|
snippet: snippet)
|
|
194
184
|
|
|
195
|
-
# If
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# Built-in: GitHub crash comment (doesn't need a plugin)
|
|
199
|
-
if source == :github
|
|
200
|
-
pr_number = source_context[:pr_number]
|
|
201
|
-
repo_name = source_context[:repo_name]
|
|
202
|
-
return unless pr_number && repo_name
|
|
203
|
-
|
|
204
|
-
work_dir = source_context[:work_dir] || Dir.pwd
|
|
205
|
-
comment_body = "💥 **#{agent_display} crashed** (exit code #{exit_status})\n\nLog: `#{log_file}`#{snippet_block}"
|
|
206
|
-
begin
|
|
207
|
-
run_cmd("gh", "pr", "comment", pr_number.to_s, "--repo", repo_name, "--body", comment_body, chdir: work_dir)
|
|
208
|
-
LOG.info "[CrashNotify] Posted crash comment on GitHub PR ##{pr_number}"
|
|
209
|
-
rescue StandardError => e
|
|
210
|
-
LOG.error "[CrashNotify] Failed to post GitHub crash comment: #{e.message}"
|
|
211
|
-
end
|
|
212
|
-
end
|
|
185
|
+
# If no plugin handled it, log a warning
|
|
186
|
+
LOG.warn "[CrashNotify] Agent crashed but no plugin handled notification (source: #{source})" unless handled.any?
|
|
213
187
|
rescue StandardError => e
|
|
214
188
|
LOG.error "[CrashNotify] Unexpected error: #{e.message}"
|
|
215
189
|
end
|
|
216
190
|
|
|
191
|
+
# Check if a prior CLI session exists in the given directory for the specified CLI binary.
|
|
192
|
+
# This prevents resume attempts when the CLI provider changed (e.g., [cli:grok] in a thread
|
|
193
|
+
# started by kiro-cli) or when the session was started on a different machine.
|
|
194
|
+
def prior_session_exists?(chdir, agent_cli)
|
|
195
|
+
return false unless chdir && agent_cli
|
|
196
|
+
|
|
197
|
+
cli_name = File.basename(agent_cli)
|
|
198
|
+
|
|
199
|
+
# Check for CLI-specific session markers:
|
|
200
|
+
# - grok uses .grok/ directory for session state
|
|
201
|
+
# - kiro-cli uses .kiro-cli/ or similar
|
|
202
|
+
# - Generic fallback: check tmp/ for agent logs from this CLI
|
|
203
|
+
session_dir = File.join(chdir, ".#{cli_name}")
|
|
204
|
+
return true if File.directory?(session_dir)
|
|
205
|
+
|
|
206
|
+
# Fallback: look for recent session logs in tmp/ that suggest this CLI ran here before.
|
|
207
|
+
# This covers CLIs that don't leave a dotdir but do leave logs via brainiac.
|
|
208
|
+
tmp_dir = File.join(chdir, "tmp")
|
|
209
|
+
return false unless File.directory?(tmp_dir)
|
|
210
|
+
|
|
211
|
+
Dir.glob(File.join(tmp_dir, "agent-*.log")).any? do |log|
|
|
212
|
+
# Only count logs from the last 24 hours as valid "resumable" sessions
|
|
213
|
+
File.mtime(log) > Time.now - 86_400
|
|
214
|
+
end
|
|
215
|
+
rescue StandardError
|
|
216
|
+
false
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Public helper: check if resume is viable for a given project + CLI provider combo.
|
|
220
|
+
# Plugins should call this BEFORE building the prompt to decide between
|
|
221
|
+
# render_resume_prompt (lean) and render_prompt (full context).
|
|
222
|
+
#
|
|
223
|
+
# Returns true if the CLI supports resume AND a prior session exists in the working directory.
|
|
224
|
+
# When this returns false, plugins should use render_prompt with thread history as card_context
|
|
225
|
+
# so the agent gets full context even though this is a follow-up message.
|
|
226
|
+
def resume_viable?(project_config:, cli_provider: nil, agent_name: nil, chdir: nil)
|
|
227
|
+
resolved = resolve_project_cli_config(project_config, cli_provider_override: cli_provider, agent_name: agent_name)
|
|
228
|
+
chdir ||= resolved["repo_path"]
|
|
229
|
+
return false unless resolved["resume_flag"]
|
|
230
|
+
|
|
231
|
+
prior_session_exists?(chdir, resolved["agent_cli"])
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Determine whether a session resume should actually happen.
|
|
235
|
+
# Returns truthy (the resume flag string) if viable, false otherwise.
|
|
236
|
+
# Logs a message when resume was requested but isn't possible.
|
|
237
|
+
def resolve_resume(resume, resolved, chdir)
|
|
238
|
+
return false unless resume && resolved["resume_flag"]
|
|
239
|
+
return resolved["resume_flag"] if prior_session_exists?(chdir, resolved["agent_cli"])
|
|
240
|
+
|
|
241
|
+
LOG.info "[Dispatch] Resume requested but not viable for #{resolved["agent_cli"]} in #{chdir} — starting fresh session"
|
|
242
|
+
false
|
|
243
|
+
end
|
|
244
|
+
|
|
217
245
|
def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil, effort: nil, agent_name: nil, card_number: nil, comment_id: nil,
|
|
218
246
|
source: nil, source_context: {}, skip_column_move: false, cli_provider: nil, resume: false)
|
|
219
247
|
resolved = resolve_project_cli_config(project_config, cli_provider_override: cli_provider, agent_name: agent_name)
|
|
@@ -222,9 +250,8 @@ def run_agent(prompt, project_config:, chdir: nil, log_name: "agent", model: nil
|
|
|
222
250
|
effort ||= resolved["agent_effort"]
|
|
223
251
|
agent_config_name = agent_name&.downcase&.gsub(/[^a-z0-9-]/, "-")
|
|
224
252
|
|
|
225
|
-
# Auto-resume: if the provider supports session
|
|
226
|
-
|
|
227
|
-
should_resume = resume && resolved["resume_flag"]
|
|
253
|
+
# Auto-resume: only if the provider supports it AND a prior session exists for this CLI here.
|
|
254
|
+
should_resume = resolve_resume(resume, resolved, chdir)
|
|
228
255
|
|
|
229
256
|
# Pre-dispatch hook — plugins can prep the working directory (e.g., copy config files, clean up)
|
|
230
257
|
Brainiac.emit(:pre_dispatch, chdir: chdir, project_config: project_config, agent_name: agent_name)
|
data/lib/brainiac/hooks.rb
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
# end
|
|
23
23
|
#
|
|
24
24
|
# Usage (in core):
|
|
25
|
-
# Brainiac.emit(:agent_completed, card_number: 42, agent_name: "
|
|
25
|
+
# Brainiac.emit(:agent_completed, card_number: 42, agent_name: "Sherlock", ...)
|
|
26
26
|
|
|
27
27
|
module Brainiac
|
|
28
28
|
@hooks = Hash.new { |h, k| h[k] = [] }
|
|
@@ -77,8 +77,8 @@ end
|
|
|
77
77
|
# Convenience: send a notification for cron job output.
|
|
78
78
|
def notify_cron_output(job, message, agent_name: nil)
|
|
79
79
|
send_notification(:cron, message,
|
|
80
|
-
target: job[:notify_target]
|
|
81
|
-
channel: job[:notify_channel]
|
|
80
|
+
target: job[:notify_target],
|
|
81
|
+
channel: job[:notify_channel],
|
|
82
82
|
agent: agent_name || job[:agent],
|
|
83
83
|
job_id: job[:id],
|
|
84
84
|
forum_title: job[:forum_title],
|
data/lib/brainiac/prompts.rb
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
#
|
|
5
5
|
# Prompts are layered:
|
|
6
6
|
# PROMPT_CORE — universal (identity, memory, brain, reflection)
|
|
7
|
-
#
|
|
7
|
+
# (GitHub prompts extracted to brainiac-github plugin)
|
|
8
8
|
#
|
|
9
9
|
# Each handler composes: PROMPT_CORE + channel rules + situation template.
|
|
10
10
|
# Channel-specific prompts (Discord, Fizzy, etc.) are registered by plugins.
|
|
@@ -112,7 +112,7 @@ PROMPT_CORE = <<~PROMPT
|
|
|
112
112
|
- Be specific in your query — tell the subagent exactly what to find and where to look
|
|
113
113
|
- Include relevant file paths and repo locations in the query
|
|
114
114
|
- Use `relevant_context` to pass information the subagent needs
|
|
115
|
-
- You can specify `agent_name` to use a specialized agent (e.g., "
|
|
115
|
+
- You can specify `agent_name` to use a specialized agent (e.g., "robin" for Android research)
|
|
116
116
|
- Run `ListAgents` first if you want to see available specialized agents
|
|
117
117
|
- Up to 4 subagents can run in parallel
|
|
118
118
|
- To discover project locations for cross-repo work, run: `brainiac list`
|
|
@@ -122,37 +122,11 @@ PROMPT_CORE = <<~PROMPT
|
|
|
122
122
|
They're excellent researchers — use them as such.
|
|
123
123
|
|
|
124
124
|
## Image Reading Limits
|
|
125
|
-
Read at most 4
|
|
125
|
+
Read at most 4-5 images per tool call. Summarize what you saw before reading more.
|
|
126
126
|
Loading too many images at once can exceed the API request size limit and crash your session.
|
|
127
127
|
|
|
128
128
|
PROMPT
|
|
129
129
|
|
|
130
|
-
# ---------------------------------------------------------------------------
|
|
131
|
-
# PROMPT_PRE_POST_CHECK — inserted before PROMPT_REFLECTION so the agent
|
|
132
|
-
# re-checks for new comments/messages before posting its response.
|
|
133
|
-
# Channel-specific: plugins register pre-post checks, Discord skips.
|
|
134
|
-
# ---------------------------------------------------------------------------
|
|
135
|
-
|
|
136
|
-
PROMPT_PRE_POST_CHECK_GITHUB = <<~PROMPT
|
|
137
|
-
## Pre-Post Comment Check (MANDATORY — do this BEFORE posting your comment)
|
|
138
|
-
|
|
139
|
-
Your session may have been running for a while. Before you post your final comment,
|
|
140
|
-
re-check the PR for new comments that arrived while you were working:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
gh pr view {{PR_NUMBER}} --comments --json comments
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
If there are **new comments** that weren't in your original context:
|
|
147
|
-
|
|
148
|
-
1. **Read them carefully** — a reviewer may have added feedback or changed direction
|
|
149
|
-
2. **Adjust your work or response** to account for the new information
|
|
150
|
-
3. **Do NOT ignore new comments** — avoid posting a response that's already outdated
|
|
151
|
-
|
|
152
|
-
If no new comments appeared, proceed normally.
|
|
153
|
-
|
|
154
|
-
PROMPT
|
|
155
|
-
|
|
156
130
|
# ---------------------------------------------------------------------------
|
|
157
131
|
# PROMPT_REFLECTION — appended AFTER the situation template so the agent
|
|
158
132
|
# sees its task first and reflects only after completing it.
|
|
@@ -186,78 +160,13 @@ PROMPT_REFLECTION = <<~PROMPT
|
|
|
186
160
|
|
|
187
161
|
PROMPT
|
|
188
162
|
|
|
189
|
-
# ---------------------------------------------------------------------------
|
|
190
|
-
# PROMPT_GITHUB_CHANNEL — GitHub-specific rules, prepended to GitHub templates
|
|
191
|
-
# ---------------------------------------------------------------------------
|
|
192
|
-
PROMPT_GITHUB_CHANNEL = <<~PROMPT
|
|
193
|
-
## GitHub Channel Rules
|
|
194
|
-
|
|
195
|
-
### Formatting
|
|
196
|
-
Use GitHub-Flavored Markdown for all comments:
|
|
197
|
-
- `## Heading` for sections
|
|
198
|
-
- `**bold**` for emphasis
|
|
199
|
-
- ``` ```language ``` for code blocks
|
|
200
|
-
- `- item` for lists
|
|
201
|
-
|
|
202
|
-
### Scope
|
|
203
|
-
You are responding to activity on a GitHub PR. Focus on the code changes and review feedback.
|
|
204
|
-
When posting comments, post on the PR unless specifically asked to update the card.
|
|
205
|
-
|
|
206
|
-
PROMPT
|
|
207
|
-
|
|
208
|
-
PROMPT_GITHUB_PR_COMMENT = <<~'PROMPT'
|
|
209
|
-
There's a new comment from @{{COMMENT_CREATOR}} on your PR #{{PR_NUMBER}} for card #{{CARD_NUMBER}}.
|
|
210
|
-
|
|
211
|
-
Comment:
|
|
212
|
-
{{COMMENT_BODY}}
|
|
213
|
-
|
|
214
|
-
Please:
|
|
215
|
-
1. Read the comment and understand what's being requested
|
|
216
|
-
2. Make any necessary changes
|
|
217
|
-
3. Commit and push your updates
|
|
218
|
-
4. Reply on the PR summarizing what you changed
|
|
219
|
-
|
|
220
|
-
You are in the worktree at {{WORKTREE_PATH}}.
|
|
221
|
-
PROMPT
|
|
222
|
-
|
|
223
|
-
PROMPT_GITHUB_PR_REVIEW = <<~'PROMPT'
|
|
224
|
-
A code review has been submitted on your PR #{{PR_NUMBER}} for card #{{CARD_NUMBER}}.
|
|
225
|
-
|
|
226
|
-
{{REVIEW_CONTEXT}}
|
|
227
|
-
|
|
228
|
-
Please:
|
|
229
|
-
1. Read the review comments carefully
|
|
230
|
-
2. Address each piece of feedback
|
|
231
|
-
3. Make the necessary code changes
|
|
232
|
-
4. Commit and push your updates
|
|
233
|
-
5. Post a comment on the PR summarizing the changes
|
|
234
|
-
|
|
235
|
-
You are in the worktree at {{WORKTREE_PATH}}.
|
|
236
|
-
PROMPT
|
|
237
|
-
|
|
238
163
|
# ---------------------------------------------------------------------------
|
|
239
164
|
# Channel constant mapping for render_prompt
|
|
165
|
+
# NOTE: GitHub prompts have been extracted to brainiac-github plugin.
|
|
166
|
+
# The plugin registers via Brainiac.register_channel_prompt(:github, ...)
|
|
240
167
|
# ---------------------------------------------------------------------------
|
|
241
|
-
PROMPT_GITHUB_UAT = <<~'PROMPT'
|
|
242
|
-
PR #{{PR_NUMBER}} has been merged into main for card #{{CARD_NUMBER}}: "{{CARD_TITLE}}"
|
|
243
168
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Your job: post a comment on card #{{CARD_NUMBER}} with clear, specific steps for how to manually test this feature in UAT. Include:
|
|
247
|
-
1. What URL(s) or screen(s) to visit
|
|
248
|
-
2. Step-by-step actions to verify the feature works
|
|
249
|
-
3. What the expected behavior should be
|
|
250
|
-
4. Any edge cases worth checking
|
|
251
|
-
5. Links to relevant pages if applicable (use the UAT/staging URL, not localhost)
|
|
252
|
-
|
|
253
|
-
Base your testing steps on the card title, the PR diff, and any card context provided. Be specific — "verify it works" is not a testing step.
|
|
254
|
-
|
|
255
|
-
Do NOT make any code changes. This is a read-only review task.
|
|
256
|
-
PROMPT
|
|
257
|
-
|
|
258
|
-
CHANNEL_PROMPTS = {
|
|
259
|
-
github: PROMPT_GITHUB_CHANNEL
|
|
260
|
-
}.freeze
|
|
169
|
+
CHANNEL_PROMPTS = {}.freeze
|
|
261
170
|
|
|
262
171
|
# ---------------------------------------------------------------------------
|
|
263
172
|
# render_prompt — composes PROMPT_CORE + channel rules + situation template
|
|
@@ -266,7 +175,7 @@ CHANNEL_PROMPTS = {
|
|
|
266
175
|
# ---------------------------------------------------------------------------
|
|
267
176
|
|
|
268
177
|
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
269
|
-
def render_prompt(template, vars = {}, brain_context: "", card_context: "", agent_name: AI_AGENT_NAME, channel:
|
|
178
|
+
def render_prompt(template, vars = {}, brain_context: "", card_context: "", agent_name: AI_AGENT_NAME, channel: nil, board_key: nil)
|
|
270
179
|
result = ""
|
|
271
180
|
result += "#{brain_context}\n" unless brain_context.empty?
|
|
272
181
|
result += card_context unless card_context.empty?
|
|
@@ -278,16 +187,12 @@ def render_prompt(template, vars = {}, brain_context: "", card_context: "", agen
|
|
|
278
187
|
|
|
279
188
|
result += template
|
|
280
189
|
|
|
281
|
-
# Pre-post comment check: plugin-registered
|
|
190
|
+
# Pre-post comment check: plugin-registered
|
|
282
191
|
plugin_pre_post = Brainiac.channel_pre_post_checks[channel]
|
|
283
|
-
if plugin_pre_post
|
|
284
|
-
result += plugin_pre_post
|
|
285
|
-
elsif channel == :github
|
|
286
|
-
result += PROMPT_PRE_POST_CHECK_GITHUB
|
|
287
|
-
end
|
|
192
|
+
result += plugin_pre_post if plugin_pre_post
|
|
288
193
|
|
|
289
|
-
# Reflection prompt — skip for
|
|
290
|
-
result += PROMPT_REFLECTION
|
|
194
|
+
# Reflection prompt — skip for now
|
|
195
|
+
# result += PROMPT_REFLECTION
|
|
291
196
|
|
|
292
197
|
vars["KNOWLEDGE_DIR"] ||= KNOWLEDGE_DIR
|
|
293
198
|
vars["MEMORY_DIR"] ||= memory_dir_for(agent_name)
|
|
@@ -345,30 +250,3 @@ def render_resume_prompt(comment_body:, comment_creator:, comment_id:, card_numb
|
|
|
345
250
|
|
|
346
251
|
lines.join("\n")
|
|
347
252
|
end
|
|
348
|
-
|
|
349
|
-
# Lean resume prompt for Discord threads. The previous session has full context
|
|
350
|
-
# (role, persona, knowledge, instructions). We only send the new message + channel history.
|
|
351
|
-
def render_discord_resume_prompt(message_body:, discord_user:, response_file:, agent_name: AI_AGENT_NAME, card_id: nil)
|
|
352
|
-
memory_dir = memory_dir_for(agent_name)
|
|
353
|
-
if card_id
|
|
354
|
-
memory_file = File.join(memory_dir, "card-#{card_id}.md")
|
|
355
|
-
FileUtils.mkdir_p(memory_dir)
|
|
356
|
-
FileUtils.touch(memory_file)
|
|
357
|
-
end
|
|
358
|
-
|
|
359
|
-
lines = []
|
|
360
|
-
lines << "## Resumed Session — New Discord Message"
|
|
361
|
-
lines << ""
|
|
362
|
-
lines << "This is a continuation of your previous session in this thread."
|
|
363
|
-
lines << "All prior context, instructions, and your previous work are still in this conversation."
|
|
364
|
-
lines << ""
|
|
365
|
-
lines << "### New Message from #{discord_user}"
|
|
366
|
-
lines << ""
|
|
367
|
-
lines << message_body
|
|
368
|
-
lines << ""
|
|
369
|
-
lines << "---"
|
|
370
|
-
lines << "**IMPORTANT: Write your response to `#{response_file}`. Do NOT reply via stdout.**"
|
|
371
|
-
lines << "All your previous instructions still apply (memory, persona, one message per session, etc.)."
|
|
372
|
-
|
|
373
|
-
lines.join("\n")
|
|
374
|
-
end
|
data/lib/brainiac/restart.rb
CHANGED
|
@@ -5,9 +5,6 @@
|
|
|
5
5
|
# When an agent works on brainiac itself (modifies code), a restart is queued.
|
|
6
6
|
# A background thread checks every 30s and only restarts when no other agents
|
|
7
7
|
# are running, preventing mid-session kills.
|
|
8
|
-
#
|
|
9
|
-
# This is NOT Discord-specific — it was previously in the Discord handler
|
|
10
|
-
# because Discord agents trigger restarts most often, but any source can queue one.
|
|
11
8
|
|
|
12
9
|
BRAINIAC_RESTART_STATE = { queued: false, triggered_by: nil }
|
|
13
10
|
BRAINIAC_RESTART_MUTEX = Mutex.new
|
data/lib/brainiac/routes/api.rb
CHANGED
|
@@ -30,16 +30,10 @@ def format_active_sessions
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
def resolve_session_agent_name(
|
|
33
|
+
def resolve_session_agent_name(_card_key, info)
|
|
34
34
|
return info[:agent_name] if info[:agent_name]
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
agent_key = if parts[0] == "discord" && parts.size >= 4
|
|
38
|
-
parts[1]
|
|
39
|
-
else
|
|
40
|
-
"Unknown"
|
|
41
|
-
end
|
|
42
|
-
agent_display_name(agent_key)
|
|
36
|
+
agent_display_name("Unknown")
|
|
43
37
|
end
|
|
44
38
|
|
|
45
39
|
def format_recent_sessions
|
|
@@ -70,20 +64,6 @@ rescue Errno::EPERM
|
|
|
70
64
|
halt 403, { error: "permission denied" }.to_json
|
|
71
65
|
end
|
|
72
66
|
|
|
73
|
-
def search_giphy(query, api_key)
|
|
74
|
-
uri = URI("https://api.giphy.com/v1/gifs/search")
|
|
75
|
-
uri.query = URI.encode_www_form(api_key: api_key, q: query, limit: 5, rating: "pg-13")
|
|
76
|
-
response = Net::HTTP.get_response(uri)
|
|
77
|
-
|
|
78
|
-
if response.code.to_i == 200
|
|
79
|
-
results = JSON.parse(response.body)["data"] || []
|
|
80
|
-
results.map { |g| { url: g.dig("images", "original", "url") || g["url"], title: g["title"] } }
|
|
81
|
-
else
|
|
82
|
-
LOG.warn "[GIF] Giphy API returned #{response.code}: #{response.body[0..200]}"
|
|
83
|
-
nil
|
|
84
|
-
end
|
|
85
|
-
end
|
|
86
|
-
|
|
87
67
|
# --- Projects ---
|
|
88
68
|
|
|
89
69
|
get "/api/projects" do
|
|
@@ -108,7 +88,6 @@ post "/api/reload" do
|
|
|
108
88
|
reload_projects!(force: true)
|
|
109
89
|
reload_agent_registry!(force: true)
|
|
110
90
|
reload_user_registry!(force: true)
|
|
111
|
-
reload_github_config!(force: true)
|
|
112
91
|
ReloadHooks.run_all!
|
|
113
92
|
{ status: "reloaded", projects: PROJECTS.keys, agents: all_agent_names.to_a, registry: AGENT_REGISTRY.keys,
|
|
114
93
|
users: USER_REGISTRY["users"].size }.to_json
|
|
@@ -283,13 +262,6 @@ get "/api/logs" do
|
|
|
283
262
|
all_lines.join.gsub(/\e\[[\d;]*[a-zA-Z]/, "").gsub(/\e\[\?[\d;]*[a-zA-Z]/, "")
|
|
284
263
|
end
|
|
285
264
|
|
|
286
|
-
# --- GIF (handled by discord plugin when installed) ---
|
|
287
|
-
|
|
288
|
-
get "/api/gif" do
|
|
289
|
-
content_type :json
|
|
290
|
-
halt 503, { error: "brainiac-discord plugin not installed (provides GIF API)" }.to_json
|
|
291
|
-
end
|
|
292
|
-
|
|
293
265
|
# --- Cron ---
|
|
294
266
|
|
|
295
267
|
get "/api/cron/script" do
|
|
@@ -332,7 +304,8 @@ post "/api/cron/add" do
|
|
|
332
304
|
script: payload["script"],
|
|
333
305
|
model: payload["model"],
|
|
334
306
|
effort: payload["effort"],
|
|
335
|
-
|
|
307
|
+
notify_channel: payload["notify_channel"],
|
|
308
|
+
notify_target: payload["notify_target"],
|
|
336
309
|
forum_title: payload["forum_title"],
|
|
337
310
|
forum_reply_to_latest: payload["forum_reply_to_latest"] || false,
|
|
338
311
|
repeat_count: payload["repeat_count"]
|
|
@@ -367,7 +340,8 @@ post "/api/cron/update" do
|
|
|
367
340
|
result = update_cron_job(
|
|
368
341
|
payload["id"],
|
|
369
342
|
schedule: payload["schedule"],
|
|
370
|
-
|
|
343
|
+
notify_target: payload["notify_target"],
|
|
344
|
+
notify_channel: payload["notify_channel"],
|
|
371
345
|
forum_title: payload["forum_title"],
|
|
372
346
|
forum_reply_to_latest: payload["forum_reply_to_latest"]
|
|
373
347
|
)
|
data/lib/brainiac/sessions.rb
CHANGED
|
@@ -179,8 +179,7 @@ def register_session(card_key, pid, log_file: nil, message_id: nil, channel_id:
|
|
|
179
179
|
end
|
|
180
180
|
end
|
|
181
181
|
|
|
182
|
-
# --- Session supersede (
|
|
183
|
-
|
|
182
|
+
# --- Session supersede (Channel follow-up within window kills previous run) ---
|
|
184
183
|
SUPERSEDE_WINDOW = 60 # seconds
|
|
185
184
|
|
|
186
185
|
# Find an active session for the same supersede key (agent+channel) started within the window.
|
data/lib/brainiac/version.rb
CHANGED
data/monitor/shared.rb
CHANGED
|
@@ -21,7 +21,7 @@ BRAINIAC_DIR = File.expand_path("~/.brainiac")
|
|
|
21
21
|
# --- Agent Config ---
|
|
22
22
|
|
|
23
23
|
# Load agent configuration from ~/.brainiac/waybar.json.
|
|
24
|
-
# Returns { "
|
|
24
|
+
# Returns { "sherlock" => { emoji: "🤖", color: "blue" }, ... }
|
|
25
25
|
def load_agent_config
|
|
26
26
|
config = JSON.parse(File.read(CONFIG_PATH, encoding: "utf-8"))
|
|
27
27
|
agents = {}
|