brute 2.0.4 → 2.0.5
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/lib/brute/events/handler.rb +6 -45
- data/lib/brute/middleware/020_system_prompt.rb +1 -1
- data/lib/brute/middleware/060_questions.rb +1 -20
- data/lib/brute/queue/file_mutation_queue.rb +4 -3
- data/lib/brute/session.rb +1 -1
- data/lib/brute/store/snapshot_store.rb +1 -0
- data/lib/brute/store/todo_store.rb +1 -0
- data/lib/brute/system_prompt.rb +3 -3
- data/lib/brute/tools/fs_read.rb +1 -1
- data/lib/brute/tools/shell.rb +37 -37
- data/lib/brute/truncation.rb +7 -7
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +1 -0
- metadata +61 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1feb1c397c976722644f4f8dd5d90e8ddc3ab372d7aea7085a08baa122b65e7
|
|
4
|
+
data.tar.gz: eeaadf2902925f6538b31affacbf997b0c26da5a331c1576b4b393935a4f3444
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7896dfb66cb867dfc7492933fc65306eedad2d4db367d844233a8dca9e7e5a0419e950547c090f42f670b38eac8cd8d343439743b31354d14911574fe49702b5
|
|
7
|
+
data.tar.gz: 4c0f483938302514b0ec111a1b531c1babd3c84d45932069178163d43b9398804a41a6f480588342de3e203bf5abcb29092b28da9793e1f8aee2e246b0a968b0
|
data/lib/brute/events/handler.rb
CHANGED
|
@@ -4,57 +4,18 @@ require 'bundler/setup'
|
|
|
4
4
|
require 'brute'
|
|
5
5
|
|
|
6
6
|
module Brute
|
|
7
|
+
# @namespace
|
|
7
8
|
module Events
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# h = event.to_h
|
|
12
|
-
# case h[:type]
|
|
13
|
-
# when :content then write(h[:data])
|
|
14
|
-
# when :tool_result then write(" ✓ #{h[:data][:name]}\n")
|
|
15
|
-
# when :log then write("[#{h[:data]}]\n")
|
|
16
|
-
# end
|
|
17
|
-
# super # forward to whatever's wrapped underneath
|
|
18
|
-
# end
|
|
19
|
-
#
|
|
20
|
-
# private
|
|
21
|
-
# def write(text); $stderr.write(text); $stderr.flush; end
|
|
22
|
-
# end
|
|
23
|
-
#
|
|
24
|
-
# class JsonlTrace < Brute::Events::Handler
|
|
25
|
-
# def initialize(inner, path:)
|
|
26
|
-
# super(inner)
|
|
27
|
-
# @file = File.open(path, "a")
|
|
28
|
-
# end
|
|
29
|
-
#
|
|
30
|
-
# def <<(event)
|
|
31
|
-
# @file.puts(JSON.generate(event.to_h))
|
|
32
|
-
# @file.flush
|
|
33
|
-
# super
|
|
34
|
-
# end
|
|
35
|
-
# end
|
|
36
|
-
#
|
|
37
|
-
# class FilterNoise < Brute::Events::Handler
|
|
38
|
-
# # Drop reasoning chunks before they reach the terminal
|
|
39
|
-
# def <<(event)
|
|
40
|
-
# return self if event.to_h[:type] == :reasoning
|
|
41
|
-
# super
|
|
42
|
-
# end
|
|
43
|
-
# end
|
|
44
|
-
#
|
|
45
|
-
# pipeline = Brute::Pipeline.new do
|
|
46
|
-
# use Brute::Middleware::EventHandler, handler_class: JsonlTrace, path: "trace.jsonl"
|
|
47
|
-
# use Brute::Middleware::EventHandler, handler_class: FilterNoise
|
|
48
|
-
# use Brute::Middleware::EventHandler, handler_class: TerminalOutput
|
|
49
|
-
# end
|
|
50
|
-
#
|
|
9
|
+
# Stackable event handler base class. Subclasses override the
|
|
10
|
+
# append method, do their thing, then call super (or don't, to
|
|
11
|
+
# swallow the event).
|
|
51
12
|
class Handler
|
|
52
13
|
def initialize(inner)
|
|
53
14
|
@inner = inner
|
|
54
15
|
end
|
|
55
16
|
|
|
56
|
-
# Default: pass through. Subclasses override
|
|
57
|
-
# then super (or don't, to swallow the event).
|
|
17
|
+
# Default: pass through. Subclasses override this method, do their
|
|
18
|
+
# thing, then call super (or don't, to swallow the event).
|
|
58
19
|
def <<(event)
|
|
59
20
|
tap do
|
|
60
21
|
@inner << event if @inner
|
|
@@ -18,7 +18,7 @@ module Brute
|
|
|
18
18
|
#
|
|
19
19
|
# use Brute::Middleware::SystemPrompt,
|
|
20
20
|
# system_prompt: Brute::SystemPrompt.build { |p, _ctx|
|
|
21
|
-
# p
|
|
21
|
+
# p.append Brute::Prompts.agent_prompt("explore")
|
|
22
22
|
# }
|
|
23
23
|
#
|
|
24
24
|
# Skips injection when env[:messages] already contains a :system
|
|
@@ -11,26 +11,7 @@ module Brute
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def call(env)
|
|
14
|
-
@app.call(env)
|
|
15
|
-
#if env[:messages].last.tool_call?
|
|
16
|
-
# questions = last_message.tool_calls.select { |_id, tc| tc.name == "question" }
|
|
17
|
-
|
|
18
|
-
# if questions.any?
|
|
19
|
-
# env[:events] << {
|
|
20
|
-
# type: :tool_call_start,
|
|
21
|
-
# data: questions.map { |_id, tc| { name: tc.name, call_id: tc.id, arguments: tc.arguments } }
|
|
22
|
-
# }
|
|
23
|
-
|
|
24
|
-
# questions.each do |_id, question|
|
|
25
|
-
# result = question.call
|
|
26
|
-
|
|
27
|
-
# env[:events] << { type: :tool_result, data: { name: tc.name, content: content } }
|
|
28
|
-
|
|
29
|
-
# env[:messages] << RubyLLM::Message.new(role: :tool, content: content, tool_call_id: tc.id)
|
|
30
|
-
# end
|
|
31
|
-
# end
|
|
32
|
-
#end
|
|
33
|
-
end
|
|
14
|
+
@app.call(env)
|
|
34
15
|
end
|
|
35
16
|
end
|
|
36
17
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Brute
|
|
4
|
+
# @namespace
|
|
4
5
|
module Queue
|
|
5
6
|
# Per-file serialization queue for concurrent tool execution.
|
|
6
7
|
#
|
|
@@ -36,9 +37,9 @@ module Brute
|
|
|
36
37
|
# sequentially in FIFO order. Calls targeting different paths
|
|
37
38
|
# proceed in parallel with zero contention.
|
|
38
39
|
#
|
|
39
|
-
# @
|
|
40
|
-
# @
|
|
41
|
-
# @
|
|
40
|
+
# @parameter path [String] The file path to serialize on.
|
|
41
|
+
# @yields {block} The mutation work to perform (snapshot, read, write, etc.)
|
|
42
|
+
# @returns Whatever the block returns.
|
|
42
43
|
def serialize(path, &block)
|
|
43
44
|
key = canonical_path(path)
|
|
44
45
|
mutex = acquire_mutex(key)
|
data/lib/brute/session.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Brute
|
|
|
20
20
|
if File.exist?(path)
|
|
21
21
|
File.foreach(path).map(&:strip).each do |line|
|
|
22
22
|
if line.present?
|
|
23
|
-
# Use push to bypass
|
|
23
|
+
# Use push to bypass append persistence (avoids re-writing existing lines)
|
|
24
24
|
session.push(RubyLLM::Message.new(**JSON.parse(line, symbolize_names: true)))
|
|
25
25
|
end
|
|
26
26
|
end
|
data/lib/brute/system_prompt.rb
CHANGED
|
@@ -10,9 +10,9 @@ module Brute
|
|
|
10
10
|
# is called with a runtime context hash (provider_name, model_name, cwd, etc).
|
|
11
11
|
#
|
|
12
12
|
# sp = Brute::SystemPrompt.build do |prompt, ctx|
|
|
13
|
-
# prompt
|
|
14
|
-
# prompt
|
|
15
|
-
# prompt
|
|
13
|
+
# prompt.append Brute::Prompts::Identity.call(ctx)
|
|
14
|
+
# prompt.append Brute::Prompts::ToneAndStyle.call(ctx)
|
|
15
|
+
# prompt.append Brute::Prompts::Environment.call(ctx)
|
|
16
16
|
# end
|
|
17
17
|
#
|
|
18
18
|
# result = sp.prepare(provider_name: "anthropic", model_name: "claude-sonnet-4-20250514", cwd: Dir.pwd)
|
data/lib/brute/tools/fs_read.rb
CHANGED
|
@@ -20,7 +20,7 @@ module Brute
|
|
|
20
20
|
# When reading completes, append "(End of file - total N lines)".
|
|
21
21
|
# 5. Binary file detection — read first 4 KB sample, check for null bytes
|
|
22
22
|
# and known binary extensions (.zip, .exe, .so, .pyc, etc.).
|
|
23
|
-
# Reject with "Cannot read binary file:
|
|
23
|
+
# Reject with "Cannot read binary file: (path)".
|
|
24
24
|
# 6. Directory listing — when file_path points to a directory, list entries
|
|
25
25
|
# (paginated, respecting limit) instead of raising an error.
|
|
26
26
|
# 7. File-not-found suggestions — on miss, scan the parent directory for
|
data/lib/brute/tools/shell.rb
CHANGED
|
@@ -61,41 +61,41 @@ module Brute
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
test do
|
|
64
|
-
it "runs a command without error" do
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
it "returns exit code" do
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
it "returns a String, not a Hash" do
|
|
75
|
-
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
it "preserves the end of output when truncating (tail mode)" do
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
it "saves full output to disk when truncated" do
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
it "accepts a timeout parameter" do
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
it "times out with a short timeout" do
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
end
|
|
64
|
+
#it "runs a command without error" do
|
|
65
|
+
# result = Brute::Tools::Shell.new.call(command: "echo hello")
|
|
66
|
+
# result.strip.should =~ /hello/
|
|
67
|
+
#end
|
|
68
|
+
|
|
69
|
+
#it "returns exit code" do
|
|
70
|
+
# result = Brute::Tools::Shell.new.call(command: "false")
|
|
71
|
+
# result.should =~ /exit code: 1/
|
|
72
|
+
#end
|
|
73
|
+
|
|
74
|
+
#it "returns a String, not a Hash" do
|
|
75
|
+
# Brute::Tools::Shell.new.call(command: "echo hello").should.be.kind_of(String)
|
|
76
|
+
#end
|
|
77
|
+
|
|
78
|
+
#it "preserves the end of output when truncating (tail mode)" do
|
|
79
|
+
# result = Brute::Tools::Shell.new.call(command: "seq 1 100000")
|
|
80
|
+
# result.should =~ /100000/
|
|
81
|
+
#end
|
|
82
|
+
|
|
83
|
+
## --- Save full output to disk ---
|
|
84
|
+
|
|
85
|
+
#it "saves full output to disk when truncated" do
|
|
86
|
+
# result = Brute::Tools::Shell.new.call(command: "seq 1 100000")
|
|
87
|
+
# result.should =~ /Full output saved to:/
|
|
88
|
+
#end
|
|
89
|
+
|
|
90
|
+
## --- Configurable timeout ---
|
|
91
|
+
|
|
92
|
+
#it "accepts a timeout parameter" do
|
|
93
|
+
# result = Brute::Tools::Shell.new.call(command: "sleep 0.1 && echo done", timeout: 10)
|
|
94
|
+
# result.should =~ /done/
|
|
95
|
+
#end
|
|
96
|
+
|
|
97
|
+
#it "times out with a short timeout" do
|
|
98
|
+
# result = Brute::Tools::Shell.new.call(command: "sleep 10", timeout: 1)
|
|
99
|
+
# result.should =~ /timed out/i
|
|
100
|
+
#end
|
|
101
101
|
end
|
data/lib/brute/truncation.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Brute
|
|
|
25
25
|
# under TRUNCATION_DIR (e.g. ~/.local/share/brute/tool-output/).
|
|
26
26
|
# Return a preview + hint pointing to the saved file.
|
|
27
27
|
# 5. Hint message — when truncated, append a contextual hint:
|
|
28
|
-
# "Full output saved to:
|
|
28
|
+
# "Full output saved to: (path). Use Read with offset/limit to
|
|
29
29
|
# view specific sections."
|
|
30
30
|
# 6. Configurable limits — allow overriding MAX_LINES / MAX_BYTES
|
|
31
31
|
# via per-call options.
|
|
@@ -47,12 +47,12 @@ module Brute
|
|
|
47
47
|
# Returns the text unchanged if it fits. Otherwise returns a
|
|
48
48
|
# truncated preview with a hint message.
|
|
49
49
|
#
|
|
50
|
-
# @
|
|
51
|
-
# @
|
|
52
|
-
# @
|
|
53
|
-
# @
|
|
54
|
-
# @
|
|
55
|
-
# @
|
|
50
|
+
# @parameter text [String] the tool output to truncate
|
|
51
|
+
# @parameter max_lines [Integer] maximum number of lines to keep
|
|
52
|
+
# @parameter max_bytes [Integer] maximum byte size to keep
|
|
53
|
+
# @parameter direction [Symbol] which end to keep
|
|
54
|
+
# @parameter truncation_dir [String, nil] directory to save full output when truncating
|
|
55
|
+
# @returns [String] the (possibly truncated) text
|
|
56
56
|
#
|
|
57
57
|
def self.truncate(text, max_lines: MAX_LINES, max_bytes: MAX_BYTES, direction: :head, truncation_dir: nil)
|
|
58
58
|
return text if text.nil? || text.empty?
|
data/lib/brute/version.rb
CHANGED
data/lib/brute.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
@@ -93,6 +93,62 @@ dependencies:
|
|
|
93
93
|
- - ">="
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rack
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.0'
|
|
103
|
+
type: :runtime
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: net-http-persistent
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: json_schemer
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '2.5'
|
|
131
|
+
type: :runtime
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '2.5'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: google-protobuf
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '4.34'
|
|
145
|
+
type: :runtime
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '4.34'
|
|
96
152
|
- !ruby/object:Gem::Dependency
|
|
97
153
|
name: rake
|
|
98
154
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -201,9 +257,11 @@ files:
|
|
|
201
257
|
- lib/brute/truncation.rb
|
|
202
258
|
- lib/brute/utils/diff.rb
|
|
203
259
|
- lib/brute/version.rb
|
|
260
|
+
homepage: https://github.com/general-intelligence-systems/brute
|
|
204
261
|
licenses:
|
|
205
262
|
- MIT
|
|
206
|
-
metadata:
|
|
263
|
+
metadata:
|
|
264
|
+
documentation_uri: https://general-intelligence-systems.github.io/brute/
|
|
207
265
|
rdoc_options: []
|
|
208
266
|
require_paths:
|
|
209
267
|
- lib
|
|
@@ -211,7 +269,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
211
269
|
requirements:
|
|
212
270
|
- - ">="
|
|
213
271
|
- !ruby/object:Gem::Version
|
|
214
|
-
version: '3.
|
|
272
|
+
version: '3.3'
|
|
215
273
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
216
274
|
requirements:
|
|
217
275
|
- - ">="
|