brainiac 0.0.14 → 0.0.15
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/bin/brainiac +71 -0
- data/lib/brainiac/agents.rb +18 -0
- data/lib/brainiac/hooks.rb +2 -0
- data/lib/brainiac/intent.rb +2 -0
- data/lib/brainiac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 677c29e7ddcf5d9718eda91175193e9ffa448be65fac5e698982009327ee3576
|
|
4
|
+
data.tar.gz: ec7c044b3661f5fdca2f462a305a53ab47c6cfd76299462fa5e3df0f83f28a3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e4f3c2af97935afe5666ae722629f2a6a15af09fef73cc8ef969a11eccbf9c315cf50831e293a381cc6d2f4b2a383bce03a3fb673c4f9294ab17cb11f3656270
|
|
7
|
+
data.tar.gz: f70117f73d55be6914223bca9cbe331e0e9ecdece704349b71eb0c40f35fdcc8da8d2fdcf9cf297b7d9fa7b91b45350a09de12271332fcac7a28cda0d984c91c
|
data/Gemfile.lock
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
brainiac (0.0.
|
|
4
|
+
brainiac (0.0.15)
|
|
5
5
|
puma (~> 7.2)
|
|
6
6
|
rackup (~> 2.3)
|
|
7
7
|
sinatra (~> 4.1)
|
|
@@ -84,7 +84,7 @@ DEPENDENCIES
|
|
|
84
84
|
CHECKSUMS
|
|
85
85
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
86
86
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
87
|
-
brainiac (0.0.
|
|
87
|
+
brainiac (0.0.15)
|
|
88
88
|
json (2.19.9) sha256=9b9025b7cdddafa38d316eca0b2358488e42d417045c1b90d216a9fefe46b79a
|
|
89
89
|
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
90
90
|
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
data/bin/brainiac
CHANGED
|
@@ -120,6 +120,65 @@ rescue StandardError
|
|
|
120
120
|
[]
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# Notify the running server to reload configs (triggers agent lifecycle hooks).
|
|
124
|
+
# Silently does nothing if the server isn't running.
|
|
125
|
+
def notify_server_reload
|
|
126
|
+
config = load_config
|
|
127
|
+
server_url = config["server_url"] || "http://localhost:4567"
|
|
128
|
+
uri = URI("#{server_url}/api/reload")
|
|
129
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
130
|
+
http.open_timeout = 2
|
|
131
|
+
http.read_timeout = 5
|
|
132
|
+
request = Net::HTTP::Post.new(uri.path)
|
|
133
|
+
http.request(request)
|
|
134
|
+
rescue StandardError
|
|
135
|
+
nil
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Run CLI-time plugin hooks for agent lifecycle events.
|
|
139
|
+
# Loads each installed plugin's CLI module and calls the hook method if defined.
|
|
140
|
+
# This runs synchronously and can prompt for interactive input.
|
|
141
|
+
def notify_plugins_agent_created(agent_key, entry)
|
|
142
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
143
|
+
return unless File.exist?(plugins_file)
|
|
144
|
+
|
|
145
|
+
plugins_config = JSON.parse(File.read(plugins_file))
|
|
146
|
+
(plugins_config["plugins"] || []).each do |pentry|
|
|
147
|
+
pname = pentry.is_a?(Hash) ? pentry["name"] : pentry.to_s
|
|
148
|
+
try_require_metadata(pname, pentry)
|
|
149
|
+
try_require_plugin_cli(pname, pentry)
|
|
150
|
+
mod = resolve_plugin_module_for(pname)
|
|
151
|
+
next unless mod.respond_to?(:on_agent_created)
|
|
152
|
+
|
|
153
|
+
mod.on_agent_created(agent_key, entry)
|
|
154
|
+
rescue StandardError => e
|
|
155
|
+
puts " ⚠ Plugin '#{pname}' hook failed: #{e.message}"
|
|
156
|
+
end
|
|
157
|
+
rescue JSON::ParserError
|
|
158
|
+
nil
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Run CLI-time plugin hooks for agent removal.
|
|
162
|
+
def notify_plugins_agent_removed(agent_key, display_name)
|
|
163
|
+
plugins_file = File.join(BRAINIAC_DIR, "plugins.json")
|
|
164
|
+
return unless File.exist?(plugins_file)
|
|
165
|
+
|
|
166
|
+
plugins_config = JSON.parse(File.read(plugins_file))
|
|
167
|
+
(plugins_config["plugins"] || []).each do |pentry|
|
|
168
|
+
pname = pentry.is_a?(Hash) ? pentry["name"] : pentry.to_s
|
|
169
|
+
try_require_metadata(pname, pentry)
|
|
170
|
+
try_require_plugin_cli(pname, pentry)
|
|
171
|
+
mod = resolve_plugin_module_for(pname)
|
|
172
|
+
next unless mod.respond_to?(:on_agent_removed)
|
|
173
|
+
|
|
174
|
+
mod.on_agent_removed(agent_key, display_name)
|
|
175
|
+
rescue StandardError => e
|
|
176
|
+
puts " ⚠ Plugin '#{pname}' hook failed: #{e.message}"
|
|
177
|
+
end
|
|
178
|
+
rescue JSON::ParserError
|
|
179
|
+
nil
|
|
180
|
+
end
|
|
181
|
+
|
|
123
182
|
def wait_for_agents_to_finish
|
|
124
183
|
sessions = fetch_active_sessions
|
|
125
184
|
return true if sessions.empty?
|
|
@@ -1873,6 +1932,12 @@ when "agent"
|
|
|
1873
1932
|
brain_init(name)
|
|
1874
1933
|
end
|
|
1875
1934
|
|
|
1935
|
+
# Let plugins prompt for additional config (e.g. Discord user ID)
|
|
1936
|
+
notify_plugins_agent_created(agent_key, entry)
|
|
1937
|
+
|
|
1938
|
+
# Notify running server to reload and trigger :agent_added hook
|
|
1939
|
+
notify_server_reload
|
|
1940
|
+
|
|
1876
1941
|
when "remove", "delete", "rm"
|
|
1877
1942
|
name = ARGV.shift
|
|
1878
1943
|
unless name
|
|
@@ -1894,6 +1959,12 @@ when "agent"
|
|
|
1894
1959
|
puts "✓ Removed agent '#{display}' (#{agent_key}) from registry"
|
|
1895
1960
|
puts " Note: Persona files at ~/.brainiac/brain/persona/#{agent_key}/ were not deleted."
|
|
1896
1961
|
|
|
1962
|
+
# Let plugins clean up their config
|
|
1963
|
+
notify_plugins_agent_removed(agent_key, display)
|
|
1964
|
+
|
|
1965
|
+
# Notify running server to reload and trigger :agent_removed hook
|
|
1966
|
+
notify_server_reload
|
|
1967
|
+
|
|
1897
1968
|
when nil
|
|
1898
1969
|
puts <<~HELP
|
|
1899
1970
|
Usage: brainiac agent <name> <command> [args]
|
data/lib/brainiac/agents.rb
CHANGED
|
@@ -50,8 +50,26 @@ AGENT_REGISTRY = load_agent_registry
|
|
|
50
50
|
def reload_agent_registry!(force: false)
|
|
51
51
|
return unless file_changed?(AGENT_REGISTRY_FILE, force: force)
|
|
52
52
|
|
|
53
|
+
old_keys = AGENT_REGISTRY.keys.to_set
|
|
53
54
|
AGENT_REGISTRY.replace(load_agent_registry)
|
|
55
|
+
new_keys = AGENT_REGISTRY.keys.to_set
|
|
54
56
|
LOG.info "Reloaded agent registry: #{AGENT_REGISTRY.keys.join(", ")}"
|
|
57
|
+
|
|
58
|
+
# Emit lifecycle hooks for added/removed agents
|
|
59
|
+
added = new_keys - old_keys
|
|
60
|
+
removed = old_keys - new_keys
|
|
61
|
+
|
|
62
|
+
added.each do |key|
|
|
63
|
+
entry = AGENT_REGISTRY[key]
|
|
64
|
+
display_name = entry.is_a?(Hash) ? (entry["display_name"] || key.capitalize) : key.capitalize
|
|
65
|
+
LOG.info "Agent added: #{display_name} (#{key})"
|
|
66
|
+
Brainiac.emit(:agent_added, agent_key: key, display_name: display_name, entry: entry)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
removed.each do |key|
|
|
70
|
+
LOG.info "Agent removed: #{key}"
|
|
71
|
+
Brainiac.emit(:agent_removed, agent_key: key)
|
|
72
|
+
end
|
|
55
73
|
end
|
|
56
74
|
|
|
57
75
|
# Get the env hash for an agent. Returns {} if none configured.
|
data/lib/brainiac/hooks.rb
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
#
|
|
10
10
|
# Events:
|
|
11
11
|
# :agent_completed — After an agent session finishes (success or failure)
|
|
12
|
+
# :agent_added — When a new agent appears in the registry (after reload)
|
|
13
|
+
# :agent_removed — When an agent is removed from the registry (after reload)
|
|
12
14
|
# :pr_merged — After a GitHub PR is merged
|
|
13
15
|
# :pr_opened — After a GitHub PR is opened
|
|
14
16
|
# :pr_reviewed — After a PR review is submitted
|
data/lib/brainiac/intent.rb
CHANGED
|
@@ -40,9 +40,11 @@ INTENT_PROMPT_TEMPLATE = <<~PROMPT
|
|
|
40
40
|
|
|
41
41
|
Rules:
|
|
42
42
|
- If the message is giving {{AGENT_NAME}} instructions, asking {{AGENT_NAME}} a question, or continuing a conversation WITH {{AGENT_NAME}} → yes
|
|
43
|
+
- If the message starts with or addresses someone else by name (e.g., "Adam, ...", "Hey Sarah", "Effie, ...") → no (it's directed at that person, not {{AGENT_NAME}})
|
|
43
44
|
- If the message is humans talking to each other and {{AGENT_NAME}} is not being addressed → no
|
|
44
45
|
- If the message is a simple acknowledgment (like "thanks", "ok", "got it") directed at {{AGENT_NAME}}'s previous work → no
|
|
45
46
|
- If the message is asking a question to another person or agent → no
|
|
47
|
+
- If the message is responding to or commenting on what someone OTHER than {{AGENT_NAME}} just said → no
|
|
46
48
|
- If uncertain, lean toward yes (better to respond unnecessarily than miss a request)
|
|
47
49
|
|
|
48
50
|
Respond with ONLY "yes" or "no" — nothing else.
|
data/lib/brainiac/version.rb
CHANGED