brute 0.4.0 → 1.0.0
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/agent.rb +14 -0
- data/lib/brute/diff.rb +24 -0
- data/lib/brute/loop/agent_stream.rb +118 -0
- data/lib/brute/loop/agent_turn.rb +520 -0
- data/lib/brute/{compactor.rb → loop/compactor.rb} +2 -0
- data/lib/brute/{doom_loop.rb → loop/doom_loop.rb} +2 -0
- data/lib/brute/loop/step.rb +332 -0
- data/lib/brute/loop/tool_call_step.rb +90 -0
- data/lib/brute/middleware/compaction_check.rb +70 -23
- data/lib/brute/middleware/doom_loop_detection.rb +110 -7
- data/lib/brute/middleware/llm_call.rb +88 -1
- data/lib/brute/middleware/message_tracking.rb +140 -10
- data/lib/brute/middleware/otel/span.rb +32 -2
- data/lib/brute/middleware/otel/token_usage.rb +38 -0
- data/lib/brute/middleware/otel/tool_calls.rb +30 -1
- data/lib/brute/middleware/otel/tool_results.rb +29 -1
- data/lib/brute/middleware/otel.rb +5 -0
- data/lib/brute/middleware/reasoning_normalizer.rb +94 -0
- data/lib/brute/middleware/retry.rb +113 -1
- data/lib/brute/middleware/session_persistence.rb +46 -3
- data/lib/brute/middleware/token_tracking.rb +78 -0
- data/lib/brute/middleware/tool_error_tracking.rb +128 -1
- data/lib/brute/middleware/tool_use_guard.rb +64 -28
- data/lib/brute/middleware/tracing.rb +63 -2
- data/lib/brute/middleware.rb +18 -0
- data/lib/brute/orchestrator/turn.rb +105 -0
- data/lib/brute/patches/buffer_nil_guard.rb +5 -0
- data/lib/brute/pipeline.rb +86 -7
- data/lib/brute/prompts/build_switch.rb +29 -0
- data/lib/brute/prompts/environment.rb +43 -0
- data/lib/brute/prompts/identity.rb +29 -0
- data/lib/brute/prompts/instructions.rb +21 -0
- data/lib/brute/prompts/max_steps.rb +25 -0
- data/lib/brute/prompts/plan_reminder.rb +25 -0
- data/lib/brute/prompts/skills.rb +13 -0
- data/lib/brute/prompts.rb +28 -0
- data/lib/brute/providers/ollama.rb +135 -0
- data/lib/brute/providers/opencode_go.rb +5 -0
- data/lib/brute/providers/opencode_zen.rb +7 -2
- data/lib/brute/providers/shell.rb +2 -2
- data/lib/brute/providers/shell_response.rb +7 -2
- data/lib/brute/providers.rb +62 -0
- data/lib/brute/queue/base_queue.rb +222 -0
- data/lib/brute/{file_mutation_queue.rb → queue/file_mutation_queue.rb} +28 -26
- data/lib/brute/queue/parallel_queue.rb +66 -0
- data/lib/brute/queue/sequential_queue.rb +63 -0
- data/lib/brute/{message_store.rb → store/message_store.rb} +155 -62
- data/lib/brute/store/session.rb +106 -0
- data/lib/brute/{snapshot_store.rb → store/snapshot_store.rb} +2 -0
- data/lib/brute/{todo_store.rb → store/todo_store.rb} +2 -0
- data/lib/brute/system_prompt.rb +101 -0
- data/lib/brute/tools/delegate.rb +59 -0
- data/lib/brute/tools/fs_patch.rb +54 -2
- data/lib/brute/tools/fs_read.rb +5 -0
- data/lib/brute/tools/fs_remove.rb +7 -2
- data/lib/brute/tools/fs_search.rb +5 -0
- data/lib/brute/tools/fs_undo.rb +7 -2
- data/lib/brute/tools/fs_write.rb +40 -2
- data/lib/brute/tools/net_fetch.rb +5 -0
- data/lib/brute/tools/question.rb +5 -0
- data/lib/brute/tools/shell.rb +5 -0
- data/lib/brute/tools/todo_read.rb +6 -1
- data/lib/brute/tools/todo_write.rb +6 -1
- data/lib/brute/tools.rb +31 -0
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +40 -204
- metadata +31 -20
- data/lib/brute/agent_stream.rb +0 -63
- data/lib/brute/hooks.rb +0 -84
- data/lib/brute/orchestrator.rb +0 -391
- data/lib/brute/session.rb +0 -161
data/lib/brute/session.rb
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "fileutils"
|
|
5
|
-
require "securerandom"
|
|
6
|
-
|
|
7
|
-
module Brute
|
|
8
|
-
# Manages session persistence. Each session is a conversation that can be
|
|
9
|
-
# saved to disk and resumed later.
|
|
10
|
-
#
|
|
11
|
-
# New directory-based layout (per-session directory):
|
|
12
|
-
#
|
|
13
|
-
# ~/.brute/sessions/{session-id}/
|
|
14
|
-
# session.meta.json # session metadata
|
|
15
|
-
# context.json # llm.rb context blob (for resumption)
|
|
16
|
-
# msg_0001.json # structured messages (OpenCode format)
|
|
17
|
-
# msg_0002.json
|
|
18
|
-
# ...
|
|
19
|
-
#
|
|
20
|
-
# Also supports the legacy flat layout for reading:
|
|
21
|
-
#
|
|
22
|
-
# ~/.brute/sessions/{session-id}.json
|
|
23
|
-
# ~/.brute/sessions/{session-id}.meta.json
|
|
24
|
-
#
|
|
25
|
-
class Session
|
|
26
|
-
attr_reader :id, :title, :path
|
|
27
|
-
|
|
28
|
-
def initialize(id: nil, dir: nil)
|
|
29
|
-
@id = id || SecureRandom.uuid
|
|
30
|
-
@base_dir = dir || File.join(Dir.home, ".brute", "sessions")
|
|
31
|
-
@session_dir = File.join(@base_dir, @id)
|
|
32
|
-
@path = File.join(@session_dir, "context.json")
|
|
33
|
-
@title = nil
|
|
34
|
-
@metadata = {}
|
|
35
|
-
FileUtils.mkdir_p(@session_dir)
|
|
36
|
-
|
|
37
|
-
# Check for legacy flat-file layout and migrate path if present
|
|
38
|
-
@legacy_path = File.join(@base_dir, "#{@id}.json")
|
|
39
|
-
@legacy_meta = File.join(@base_dir, "#{@id}.meta.json")
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Returns a MessageStore for this session's structured messages.
|
|
43
|
-
def message_store
|
|
44
|
-
@message_store ||= MessageStore.new(session_id: @id, dir: @session_dir)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Save a context to this session.
|
|
48
|
-
def save(context, title: nil, metadata: {})
|
|
49
|
-
@title = title if title
|
|
50
|
-
@metadata.merge!(metadata)
|
|
51
|
-
|
|
52
|
-
# Use llm.rb's built-in serialization for context (used for resumption)
|
|
53
|
-
context.save(path: @path)
|
|
54
|
-
|
|
55
|
-
# Write metadata sidecar
|
|
56
|
-
save_meta
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Restore a context from this session.
|
|
60
|
-
# Returns true if restored successfully, false if no session file found.
|
|
61
|
-
def restore(context)
|
|
62
|
-
# Try new layout first, then legacy
|
|
63
|
-
ctx_path = if File.exist?(@path)
|
|
64
|
-
@path
|
|
65
|
-
elsif File.exist?(@legacy_path)
|
|
66
|
-
@legacy_path
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
return false unless ctx_path
|
|
70
|
-
|
|
71
|
-
context.restore(path: ctx_path)
|
|
72
|
-
|
|
73
|
-
# Load metadata
|
|
74
|
-
load_meta
|
|
75
|
-
|
|
76
|
-
true
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# List all saved sessions, newest first.
|
|
80
|
-
# Scans both new directory-based layout and legacy flat files.
|
|
81
|
-
def self.list(dir: nil)
|
|
82
|
-
dir ||= File.join(Dir.home, ".brute", "sessions")
|
|
83
|
-
return [] unless File.directory?(dir)
|
|
84
|
-
|
|
85
|
-
sessions = {}
|
|
86
|
-
|
|
87
|
-
# New layout: {id}/session.meta.json
|
|
88
|
-
Dir.glob(File.join(dir, "*", "session.meta.json")).each do |meta_path|
|
|
89
|
-
data = JSON.parse(File.read(meta_path), symbolize_names: true)
|
|
90
|
-
id = data[:id]
|
|
91
|
-
next unless id
|
|
92
|
-
sessions[id] = {
|
|
93
|
-
id: id,
|
|
94
|
-
title: data[:title],
|
|
95
|
-
saved_at: data[:saved_at],
|
|
96
|
-
path: File.join(File.dirname(meta_path), "context.json"),
|
|
97
|
-
}
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
# Legacy layout: {id}.meta.json (only if not already found)
|
|
101
|
-
Dir.glob(File.join(dir, "*.meta.json")).each do |meta_path|
|
|
102
|
-
# Skip files inside session subdirectories
|
|
103
|
-
next if meta_path.include?("/session.meta.json")
|
|
104
|
-
data = JSON.parse(File.read(meta_path), symbolize_names: true)
|
|
105
|
-
id = data[:id]
|
|
106
|
-
next unless id
|
|
107
|
-
next if sessions.key?(id) # new layout takes precedence
|
|
108
|
-
sessions[id] = {
|
|
109
|
-
id: id,
|
|
110
|
-
title: data[:title],
|
|
111
|
-
saved_at: data[:saved_at],
|
|
112
|
-
path: meta_path.sub(/\.meta\.json$/, ".json"),
|
|
113
|
-
}
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
sessions.values.sort_by { |s| s[:saved_at] || "" }.reverse
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Delete a session from disk (both new and legacy layouts).
|
|
120
|
-
def delete
|
|
121
|
-
# New layout: remove the whole directory
|
|
122
|
-
FileUtils.rm_rf(@session_dir) if File.directory?(@session_dir)
|
|
123
|
-
|
|
124
|
-
# Legacy layout: remove flat files
|
|
125
|
-
File.delete(@legacy_path) if File.exist?(@legacy_path)
|
|
126
|
-
File.delete(@legacy_meta) if File.exist?(@legacy_meta)
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
private
|
|
130
|
-
|
|
131
|
-
def meta_path
|
|
132
|
-
File.join(@session_dir, "session.meta.json")
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def save_meta
|
|
136
|
-
data = {
|
|
137
|
-
id: @id,
|
|
138
|
-
title: @title,
|
|
139
|
-
saved_at: Time.now.iso8601,
|
|
140
|
-
metadata: @metadata,
|
|
141
|
-
}
|
|
142
|
-
FileUtils.mkdir_p(@session_dir)
|
|
143
|
-
File.write(meta_path, JSON.pretty_generate(data))
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def load_meta
|
|
147
|
-
# Try new layout first
|
|
148
|
-
path = if File.exist?(meta_path)
|
|
149
|
-
meta_path
|
|
150
|
-
elsif File.exist?(@legacy_meta)
|
|
151
|
-
@legacy_meta
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
return unless path
|
|
155
|
-
|
|
156
|
-
data = JSON.parse(File.read(path), symbolize_names: true)
|
|
157
|
-
@title = data[:title]
|
|
158
|
-
@metadata = data[:metadata] || {}
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|