solid_loop 0.0.4
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 +7 -0
- data/CHANGELOG.md +156 -0
- data/CODE_OF_CONDUCT.md +67 -0
- data/CONTRIBUTING.md +82 -0
- data/MIT-LICENSE +21 -0
- data/README.md +483 -0
- data/Rakefile +11 -0
- data/app/assets/javascripts/solid_loop/chart.umd.min.js +14 -0
- data/app/assets/javascripts/solid_loop/chartjs-adapter-date-fns.bundle.min.js +7 -0
- data/app/assets/javascripts/solid_loop/chartkick.min.js +2 -0
- data/app/assets/stylesheets/solid_loop/application.css +15 -0
- data/app/controllers/solid_loop/application_controller.rb +29 -0
- data/app/controllers/solid_loop/dashboard_controller.rb +104 -0
- data/app/controllers/solid_loop/events_controller.rb +12 -0
- data/app/controllers/solid_loop/loops_controller.rb +77 -0
- data/app/controllers/solid_loop/mcp_inbound_sessions_controller.rb +13 -0
- data/app/controllers/solid_loop/mcp_sessions_controller.rb +68 -0
- data/app/controllers/solid_loop/mcp_tools_controller.rb +16 -0
- data/app/controllers/solid_loop/messages_controller.rb +12 -0
- data/app/controllers/solid_loop/tool_calls_controller.rb +12 -0
- data/app/helpers/solid_loop/application_helper.rb +125 -0
- data/app/jobs/solid_loop/application_job.rb +13 -0
- data/app/jobs/solid_loop/llm_completion_job.rb +139 -0
- data/app/jobs/solid_loop/observe_broadcast_job.rb +15 -0
- data/app/jobs/solid_loop/reaper_job.rb +20 -0
- data/app/jobs/solid_loop/tool_execution_job.rb +200 -0
- data/app/models/solid_loop/application_record.rb +5 -0
- data/app/models/solid_loop/base.rb +190 -0
- data/app/models/solid_loop/event.rb +7 -0
- data/app/models/solid_loop/loop.rb +225 -0
- data/app/models/solid_loop/mcp_inbound_session.rb +25 -0
- data/app/models/solid_loop/mcp_session.rb +12 -0
- data/app/models/solid_loop/mcp_tool.rb +26 -0
- data/app/models/solid_loop/message.rb +69 -0
- data/app/models/solid_loop/tool_call.rb +42 -0
- data/app/queries/solid_loop/admin/base_query.rb +23 -0
- data/app/queries/solid_loop/admin/events_query.rb +31 -0
- data/app/queries/solid_loop/admin/loops_query.rb +38 -0
- data/app/queries/solid_loop/admin/mcp_inbound_sessions_query.rb +31 -0
- data/app/queries/solid_loop/admin/mcp_sessions_query.rb +26 -0
- data/app/queries/solid_loop/admin/mcp_tools_query.rb +26 -0
- data/app/queries/solid_loop/admin/messages_query.rb +37 -0
- data/app/queries/solid_loop/admin/tool_calls_query.rb +34 -0
- data/app/services/solid_loop/adapters/native.rb +170 -0
- data/app/services/solid_loop/dialects/anthropic.rb +160 -0
- data/app/services/solid_loop/dialects/gemini.rb +122 -0
- data/app/services/solid_loop/dialects/open_ai.rb +62 -0
- data/app/services/solid_loop/dialects/reasoning_packer.rb +37 -0
- data/app/services/solid_loop/llm_usage_parser/anthropic.rb +22 -0
- data/app/services/solid_loop/llm_usage_parser/gemini.rb +18 -0
- data/app/services/solid_loop/llm_usage_parser/llama.rb +15 -0
- data/app/services/solid_loop/llm_usage_parser/openai.rb +18 -0
- data/app/services/solid_loop/llm_usage_parser.rb +19 -0
- data/app/services/solid_loop/mcp_session_initializer.rb +205 -0
- data/app/services/solid_loop/mcp_tool_execution_service.rb +153 -0
- data/app/services/solid_loop/middlewares/agent_initialization.rb +37 -0
- data/app/services/solid_loop/middlewares/error_handling.rb +107 -0
- data/app/services/solid_loop/middlewares/event_logging.rb +100 -0
- data/app/services/solid_loop/middlewares/message_building.rb +92 -0
- data/app/services/solid_loop/middlewares/network_calling.rb +84 -0
- data/app/services/solid_loop/middlewares/response_parsing.rb +348 -0
- data/app/services/solid_loop/middlewares/tool_call_xml_parser.rb +117 -0
- data/app/services/solid_loop/sse_stream_aggregator.rb +105 -0
- data/app/services/solid_loop/tool_failure_reconciler.rb +122 -0
- data/app/services/solid_loop/tool_middlewares/agent_initialization.rb +17 -0
- data/app/services/solid_loop/tool_middlewares/error_handling.rb +66 -0
- data/app/services/solid_loop/tool_middlewares/event_logging.rb +27 -0
- data/app/services/solid_loop/tool_middlewares/response_creation.rb +110 -0
- data/app/services/solid_loop/tool_middlewares/tool_execution.rb +206 -0
- data/app/views/layouts/solid_loop/admin.html.erb +416 -0
- data/app/views/layouts/solid_loop/application.html.erb +17 -0
- data/app/views/solid_loop/dashboard/index.html.erb +184 -0
- data/app/views/solid_loop/events/index.html.erb +47 -0
- data/app/views/solid_loop/events/show.html.erb +76 -0
- data/app/views/solid_loop/loops/index.html.erb +83 -0
- data/app/views/solid_loop/loops/show.html.erb +148 -0
- data/app/views/solid_loop/mcp_inbound_sessions/index.html.erb +53 -0
- data/app/views/solid_loop/mcp_inbound_sessions/show.html.erb +78 -0
- data/app/views/solid_loop/mcp_sessions/_tool_output.html.erb +11 -0
- data/app/views/solid_loop/mcp_sessions/index.html.erb +46 -0
- data/app/views/solid_loop/mcp_sessions/inspector.html.erb +94 -0
- data/app/views/solid_loop/mcp_sessions/show.html.erb +142 -0
- data/app/views/solid_loop/mcp_tools/index.html.erb +44 -0
- data/app/views/solid_loop/mcp_tools/show.html.erb +69 -0
- data/app/views/solid_loop/messages/_message.html.erb +75 -0
- data/app/views/solid_loop/messages/index.html.erb +80 -0
- data/app/views/solid_loop/messages/show.html.erb +121 -0
- data/app/views/solid_loop/shared/_pagination.html.erb +21 -0
- data/app/views/solid_loop/tool_calls/index.html.erb +53 -0
- data/app/views/solid_loop/tool_calls/show.html.erb +59 -0
- data/config/routes.rb +27 -0
- data/db/migrate/20260408000100_solid_loop_init.rb +136 -0
- data/db/migrate/20260709000100_solid_loop_create_mcp_inbound_sessions.rb +16 -0
- data/db/migrate/20260713000100_solid_loop_add_execution_guards.rb +6 -0
- data/db/migrate/20260714000100_solid_loop_add_message_steering.rb +17 -0
- data/db/migrate/20260714000200_solid_loop_add_message_conversation_order.rb +14 -0
- data/db/migrate/20260715000100_solid_loop_add_mcp_inbound_session_principal_key.rb +43 -0
- data/db/migrate/20260715000200_solid_loop_add_llm_lease.rb +40 -0
- data/db/migrate/20260715000300_solid_loop_add_tool_lease.rb +33 -0
- data/db/migrate/20260715000400_solid_loop_add_lease_running_check.rb +32 -0
- data/db/migrate/20260715000500_solid_loop_add_tool_lease_pair_check.rb +35 -0
- data/docs/contributing/coverage.md +64 -0
- data/docs/decisions/durable_attempt_lease.md +364 -0
- data/docs/decisions/mcp-only-tooling.md +135 -0
- data/docs/decisions/mcp-server.md +223 -0
- data/docs/decisions/reasoning_persistence.md +51 -0
- data/docs/decisions/ruby_llm_rejected.md +35 -0
- data/docs/guides/dialects.md +55 -0
- data/docs/guides/llm_middlewares.md +294 -0
- data/docs/guides/mcp_transports.md +374 -0
- data/docs/guides/tool_middlewares.md +148 -0
- data/lib/solid_loop/configuration.rb +175 -0
- data/lib/solid_loop/engine.rb +14 -0
- data/lib/solid_loop/lease_heartbeat.rb +94 -0
- data/lib/solid_loop/lease_renewer.rb +347 -0
- data/lib/solid_loop/llm_metrics.rb +16 -0
- data/lib/solid_loop/mcp/call_context.rb +19 -0
- data/lib/solid_loop/mcp/client_factory.rb +61 -0
- data/lib/solid_loop/mcp/http_transport.rb +52 -0
- data/lib/solid_loop/mcp/principal.rb +82 -0
- data/lib/solid_loop/mcp/result.rb +13 -0
- data/lib/solid_loop/mcp/server.rb +246 -0
- data/lib/solid_loop/mcp/stdio_transport.rb +224 -0
- data/lib/solid_loop/mcp/toolset.rb +347 -0
- data/lib/solid_loop/mcp/transport.rb +20 -0
- data/lib/solid_loop/mcp.rb +25 -0
- data/lib/solid_loop/mcp_client.rb +176 -0
- data/lib/solid_loop/pipeline/builder.rb +38 -0
- data/lib/solid_loop/pipeline/context.rb +61 -0
- data/lib/solid_loop/pipeline/tool_context.rb +53 -0
- data/lib/solid_loop/pipeline.rb +40 -0
- data/lib/solid_loop/reaper.rb +313 -0
- data/lib/solid_loop/version.rb +3 -0
- data/lib/solid_loop.rb +94 -0
- data/lib/tasks/coverage.rake +206 -0
- data/lib/tasks/solid_loop_tasks.rake +4 -0
- metadata +228 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
namespace :coverage do
|
|
4
|
+
GROUPS = %w[
|
|
5
|
+
app/controllers app/models app/services app/helpers
|
|
6
|
+
app/jobs app/mailers app/queries app/channels
|
|
7
|
+
app/concerns app/views
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
desc "Parse coverage/coverage.json and print summary. Args: sort (worst|partial|uncovered|full), filter, git (staged|head|branch)"
|
|
11
|
+
task :report, [:sort, :filter, :git] do |_t, args|
|
|
12
|
+
CoverageReporter.report(args)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class CoverageReporter
|
|
16
|
+
COVERAGE_PATH = File.expand_path("../../coverage/coverage.json", __dir__)
|
|
17
|
+
|
|
18
|
+
def self.report(args)
|
|
19
|
+
new(args).report
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(args)
|
|
23
|
+
@args = args
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def report
|
|
27
|
+
unless File.exist?(COVERAGE_PATH)
|
|
28
|
+
abort "coverage/coverage.json not found. Run tests first:\n bundle exec rspec"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
data = JSON.parse(File.read(COVERAGE_PATH))
|
|
32
|
+
raw_coverage = parse_coverage(data)
|
|
33
|
+
|
|
34
|
+
git_mode = @args[:git]
|
|
35
|
+
git_changed_lines = git_mode ? load_git_changes(git_mode) : {}
|
|
36
|
+
|
|
37
|
+
if git_mode && git_changed_lines.empty?
|
|
38
|
+
puts "No changed files found for git mode: #{git_mode}"
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
filter_pattern = @args[:filter] ? Regexp.new(@args[:filter], Regexp::IGNORECASE) : nil
|
|
43
|
+
file_stats = []
|
|
44
|
+
total_lines = 0
|
|
45
|
+
covered_lines = 0
|
|
46
|
+
|
|
47
|
+
raw_coverage.each do |file_path, line_data|
|
|
48
|
+
next unless file_path.include?("/app/")
|
|
49
|
+
|
|
50
|
+
lines = extract_lines(line_data)
|
|
51
|
+
next if lines.nil?
|
|
52
|
+
|
|
53
|
+
short = file_path.sub(%r{.*/(?=app/)}, "")
|
|
54
|
+
next if filter_pattern && short !~ filter_pattern
|
|
55
|
+
next if git_mode && !git_changed_lines.key?(short)
|
|
56
|
+
|
|
57
|
+
stat = git_mode ? stat_for_changed(short, lines, git_changed_lines[short]) : stat_for_all(short, lines)
|
|
58
|
+
next if stat.nil?
|
|
59
|
+
|
|
60
|
+
total_lines += stat[:total]
|
|
61
|
+
covered_lines += stat[:covered]
|
|
62
|
+
file_stats << stat
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if file_stats.empty?
|
|
66
|
+
puts "No matching files found."
|
|
67
|
+
return
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
overall = total_lines > 0 ? (covered_lines.to_f / total_lines * 100).round(2) : 0.0
|
|
71
|
+
header = ["Coverage Report", filter_pattern && "[filter: #{@args[:filter]}]", git_mode && "[git: #{git_mode}]"].compact.join(" ")
|
|
72
|
+
|
|
73
|
+
puts "=" * 60
|
|
74
|
+
puts " #{header}"
|
|
75
|
+
puts " #{covered_lines}/#{total_lines} lines (#{overall}%)"
|
|
76
|
+
puts "=" * 60
|
|
77
|
+
|
|
78
|
+
sort_mode = (@args[:sort] || "worst").to_sym
|
|
79
|
+
|
|
80
|
+
file_stats.group_by { |f| f[:group] }.sort.each do |group_name, files|
|
|
81
|
+
g_total = files.sum { |f| f[:total] }
|
|
82
|
+
g_covered = files.sum { |f| f[:covered] }
|
|
83
|
+
g_pct = g_total > 0 ? (g_covered.to_f / g_total * 100).round(1) : 0.0
|
|
84
|
+
|
|
85
|
+
filtered = apply_sort(files, sort_mode)
|
|
86
|
+
next if filtered.empty?
|
|
87
|
+
|
|
88
|
+
label = git_mode ? "#{group_name} (changed lines)" : group_name
|
|
89
|
+
puts "\n--- #{label} (#{g_pct}% | #{g_covered}/#{g_total}) ---"
|
|
90
|
+
filtered.each do |f|
|
|
91
|
+
tag = f[:changed_only] ? " [changed]" : ""
|
|
92
|
+
puts " #{f[:pct].to_s.rjust(5)}% #{f[:path]}#{tag} (#{f[:missed]} missed of #{f[:total]})"
|
|
93
|
+
next if f[:missed_lines].empty?
|
|
94
|
+
puts " uncovered: #{format_line_ranges(f[:missed_lines])}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
puts
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
def parse_coverage(data)
|
|
104
|
+
if data["files"].is_a?(Array)
|
|
105
|
+
data["files"].each_with_object({}) { |f, h| h[f["filename"]] = f["coverage"] }
|
|
106
|
+
else
|
|
107
|
+
data["coverage"] || {}
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def extract_lines(line_data)
|
|
112
|
+
case line_data
|
|
113
|
+
when Hash then line_data["lines"]
|
|
114
|
+
when Array then line_data
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def stat_for_changed(short, lines, changed_line_nums)
|
|
119
|
+
relevant = lines.each_with_index.select { |l, i| !l.nil? && changed_line_nums.include?(i + 1) }
|
|
120
|
+
cov = relevant.count { |l, _| l > 0 }
|
|
121
|
+
miss = relevant.count { |l, _| l == 0 }
|
|
122
|
+
return nil if (cov + miss) == 0
|
|
123
|
+
build_stat(short, cov, miss, relevant.select { |l, _| l == 0 }.map { |_, i| i + 1 }.sort, changed_only: true)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def stat_for_all(short, lines)
|
|
127
|
+
relevant = lines.compact
|
|
128
|
+
cov = relevant.count { |l| l > 0 }
|
|
129
|
+
miss = relevant.count { |l| l == 0 }
|
|
130
|
+
miss_lines = lines.each_with_index.select { |l, _| l == 0 }.map { |_, i| i + 1 }.sort
|
|
131
|
+
return nil if (cov + miss) == 0
|
|
132
|
+
build_stat(short, cov, miss, miss_lines, changed_only: false)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def build_stat(short, cov, miss, miss_lines, changed_only:)
|
|
136
|
+
tot = cov + miss
|
|
137
|
+
{
|
|
138
|
+
path: short,
|
|
139
|
+
pct: (cov.to_f / tot * 100).round(1),
|
|
140
|
+
covered: cov,
|
|
141
|
+
total: tot,
|
|
142
|
+
missed: miss,
|
|
143
|
+
missed_lines: miss_lines,
|
|
144
|
+
group: detect_group(short),
|
|
145
|
+
changed_only: changed_only
|
|
146
|
+
}
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def detect_group(short)
|
|
150
|
+
GROUPS.find { |prefix| short.start_with?(prefix) } || "app/other"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def apply_sort(files, mode)
|
|
154
|
+
case mode
|
|
155
|
+
when :uncovered then files.select { |f| f[:pct] == 0.0 }.sort_by { |f| -f[:total] }
|
|
156
|
+
when :partial then files.select { |f| f[:pct] > 0 && f[:pct] < 100 }.sort_by { |f| f[:pct] }
|
|
157
|
+
when :full then files.select { |f| f[:pct] == 100.0 }.sort_by { |f| -f[:total] }
|
|
158
|
+
else files.sort_by { |f| f[:pct] }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def format_line_ranges(line_nums)
|
|
163
|
+
return "" if line_nums.empty?
|
|
164
|
+
ranges, start = [], line_nums[0]
|
|
165
|
+
finish = start
|
|
166
|
+
line_nums[1..].each do |n|
|
|
167
|
+
n == finish + 1 ? (finish = n) : (ranges << range_str(start, finish); start = finish = n)
|
|
168
|
+
end
|
|
169
|
+
(ranges << range_str(start, finish)).join(", ")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def range_str(s, f) = s == f ? s.to_s : "#{s}-#{f}"
|
|
173
|
+
|
|
174
|
+
def load_git_changes(mode)
|
|
175
|
+
diff_output = case mode
|
|
176
|
+
when "staged" then `git diff --cached --diff-filter=ACMR --unified=0 -- app/`
|
|
177
|
+
when "unstaged" then `git diff --diff-filter=ACMR --unified=0 -- app/`
|
|
178
|
+
when "head"
|
|
179
|
+
`git diff HEAD~1...HEAD --diff-filter=ACMR --unified=0 -- app/`
|
|
180
|
+
when "branch"
|
|
181
|
+
branch = `git rev-parse --abbrev-ref HEAD`.strip
|
|
182
|
+
base = `git merge-base main #{branch} 2>/dev/null`.strip
|
|
183
|
+
base = `git merge-base master #{branch} 2>/dev/null`.strip if base.empty?
|
|
184
|
+
base.empty? ? `git diff HEAD --diff-filter=ACMR --unified=0 -- app/` \
|
|
185
|
+
: `git diff #{base}...HEAD --diff-filter=ACMR --unified=0 -- app/`
|
|
186
|
+
else
|
|
187
|
+
`git diff HEAD --diff-filter=ACMR --unified=0 -- app/`
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
result = {}
|
|
191
|
+
current_file = nil
|
|
192
|
+
diff_output.each_line do |line|
|
|
193
|
+
if line =~ %r{^\+\+\+ b/(app/.+)$}
|
|
194
|
+
current_file = $1.strip
|
|
195
|
+
result[current_file] ||= []
|
|
196
|
+
elsif line =~ /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/
|
|
197
|
+
next unless current_file
|
|
198
|
+
start_line = $1.to_i
|
|
199
|
+
count = [($2 || "1").to_i, 1].max
|
|
200
|
+
result[current_file].concat((start_line...(start_line + count)).to_a)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
result
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: solid_loop
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.4
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ruslan
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: rails
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: faraday
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '2.0'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.0'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: event_stream_parser
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '1.0'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '1.0'
|
|
60
|
+
description: Infrastructure layer for running autonomous AI agents as long-lived Rails
|
|
61
|
+
background processes (ActiveJob + PostgreSQL) using Middleware architecture and
|
|
62
|
+
MCP tools.
|
|
63
|
+
email:
|
|
64
|
+
- ru.ruslan@gmail.com
|
|
65
|
+
executables: []
|
|
66
|
+
extensions: []
|
|
67
|
+
extra_rdoc_files: []
|
|
68
|
+
files:
|
|
69
|
+
- CHANGELOG.md
|
|
70
|
+
- CODE_OF_CONDUCT.md
|
|
71
|
+
- CONTRIBUTING.md
|
|
72
|
+
- MIT-LICENSE
|
|
73
|
+
- README.md
|
|
74
|
+
- Rakefile
|
|
75
|
+
- app/assets/javascripts/solid_loop/chart.umd.min.js
|
|
76
|
+
- app/assets/javascripts/solid_loop/chartjs-adapter-date-fns.bundle.min.js
|
|
77
|
+
- app/assets/javascripts/solid_loop/chartkick.min.js
|
|
78
|
+
- app/assets/stylesheets/solid_loop/application.css
|
|
79
|
+
- app/controllers/solid_loop/application_controller.rb
|
|
80
|
+
- app/controllers/solid_loop/dashboard_controller.rb
|
|
81
|
+
- app/controllers/solid_loop/events_controller.rb
|
|
82
|
+
- app/controllers/solid_loop/loops_controller.rb
|
|
83
|
+
- app/controllers/solid_loop/mcp_inbound_sessions_controller.rb
|
|
84
|
+
- app/controllers/solid_loop/mcp_sessions_controller.rb
|
|
85
|
+
- app/controllers/solid_loop/mcp_tools_controller.rb
|
|
86
|
+
- app/controllers/solid_loop/messages_controller.rb
|
|
87
|
+
- app/controllers/solid_loop/tool_calls_controller.rb
|
|
88
|
+
- app/helpers/solid_loop/application_helper.rb
|
|
89
|
+
- app/jobs/solid_loop/application_job.rb
|
|
90
|
+
- app/jobs/solid_loop/llm_completion_job.rb
|
|
91
|
+
- app/jobs/solid_loop/observe_broadcast_job.rb
|
|
92
|
+
- app/jobs/solid_loop/reaper_job.rb
|
|
93
|
+
- app/jobs/solid_loop/tool_execution_job.rb
|
|
94
|
+
- app/models/solid_loop/application_record.rb
|
|
95
|
+
- app/models/solid_loop/base.rb
|
|
96
|
+
- app/models/solid_loop/event.rb
|
|
97
|
+
- app/models/solid_loop/loop.rb
|
|
98
|
+
- app/models/solid_loop/mcp_inbound_session.rb
|
|
99
|
+
- app/models/solid_loop/mcp_session.rb
|
|
100
|
+
- app/models/solid_loop/mcp_tool.rb
|
|
101
|
+
- app/models/solid_loop/message.rb
|
|
102
|
+
- app/models/solid_loop/tool_call.rb
|
|
103
|
+
- app/queries/solid_loop/admin/base_query.rb
|
|
104
|
+
- app/queries/solid_loop/admin/events_query.rb
|
|
105
|
+
- app/queries/solid_loop/admin/loops_query.rb
|
|
106
|
+
- app/queries/solid_loop/admin/mcp_inbound_sessions_query.rb
|
|
107
|
+
- app/queries/solid_loop/admin/mcp_sessions_query.rb
|
|
108
|
+
- app/queries/solid_loop/admin/mcp_tools_query.rb
|
|
109
|
+
- app/queries/solid_loop/admin/messages_query.rb
|
|
110
|
+
- app/queries/solid_loop/admin/tool_calls_query.rb
|
|
111
|
+
- app/services/solid_loop/adapters/native.rb
|
|
112
|
+
- app/services/solid_loop/dialects/anthropic.rb
|
|
113
|
+
- app/services/solid_loop/dialects/gemini.rb
|
|
114
|
+
- app/services/solid_loop/dialects/open_ai.rb
|
|
115
|
+
- app/services/solid_loop/dialects/reasoning_packer.rb
|
|
116
|
+
- app/services/solid_loop/llm_usage_parser.rb
|
|
117
|
+
- app/services/solid_loop/llm_usage_parser/anthropic.rb
|
|
118
|
+
- app/services/solid_loop/llm_usage_parser/gemini.rb
|
|
119
|
+
- app/services/solid_loop/llm_usage_parser/llama.rb
|
|
120
|
+
- app/services/solid_loop/llm_usage_parser/openai.rb
|
|
121
|
+
- app/services/solid_loop/mcp_session_initializer.rb
|
|
122
|
+
- app/services/solid_loop/mcp_tool_execution_service.rb
|
|
123
|
+
- app/services/solid_loop/middlewares/agent_initialization.rb
|
|
124
|
+
- app/services/solid_loop/middlewares/error_handling.rb
|
|
125
|
+
- app/services/solid_loop/middlewares/event_logging.rb
|
|
126
|
+
- app/services/solid_loop/middlewares/message_building.rb
|
|
127
|
+
- app/services/solid_loop/middlewares/network_calling.rb
|
|
128
|
+
- app/services/solid_loop/middlewares/response_parsing.rb
|
|
129
|
+
- app/services/solid_loop/middlewares/tool_call_xml_parser.rb
|
|
130
|
+
- app/services/solid_loop/sse_stream_aggregator.rb
|
|
131
|
+
- app/services/solid_loop/tool_failure_reconciler.rb
|
|
132
|
+
- app/services/solid_loop/tool_middlewares/agent_initialization.rb
|
|
133
|
+
- app/services/solid_loop/tool_middlewares/error_handling.rb
|
|
134
|
+
- app/services/solid_loop/tool_middlewares/event_logging.rb
|
|
135
|
+
- app/services/solid_loop/tool_middlewares/response_creation.rb
|
|
136
|
+
- app/services/solid_loop/tool_middlewares/tool_execution.rb
|
|
137
|
+
- app/views/layouts/solid_loop/admin.html.erb
|
|
138
|
+
- app/views/layouts/solid_loop/application.html.erb
|
|
139
|
+
- app/views/solid_loop/dashboard/index.html.erb
|
|
140
|
+
- app/views/solid_loop/events/index.html.erb
|
|
141
|
+
- app/views/solid_loop/events/show.html.erb
|
|
142
|
+
- app/views/solid_loop/loops/index.html.erb
|
|
143
|
+
- app/views/solid_loop/loops/show.html.erb
|
|
144
|
+
- app/views/solid_loop/mcp_inbound_sessions/index.html.erb
|
|
145
|
+
- app/views/solid_loop/mcp_inbound_sessions/show.html.erb
|
|
146
|
+
- app/views/solid_loop/mcp_sessions/_tool_output.html.erb
|
|
147
|
+
- app/views/solid_loop/mcp_sessions/index.html.erb
|
|
148
|
+
- app/views/solid_loop/mcp_sessions/inspector.html.erb
|
|
149
|
+
- app/views/solid_loop/mcp_sessions/show.html.erb
|
|
150
|
+
- app/views/solid_loop/mcp_tools/index.html.erb
|
|
151
|
+
- app/views/solid_loop/mcp_tools/show.html.erb
|
|
152
|
+
- app/views/solid_loop/messages/_message.html.erb
|
|
153
|
+
- app/views/solid_loop/messages/index.html.erb
|
|
154
|
+
- app/views/solid_loop/messages/show.html.erb
|
|
155
|
+
- app/views/solid_loop/shared/_pagination.html.erb
|
|
156
|
+
- app/views/solid_loop/tool_calls/index.html.erb
|
|
157
|
+
- app/views/solid_loop/tool_calls/show.html.erb
|
|
158
|
+
- config/routes.rb
|
|
159
|
+
- db/migrate/20260408000100_solid_loop_init.rb
|
|
160
|
+
- db/migrate/20260709000100_solid_loop_create_mcp_inbound_sessions.rb
|
|
161
|
+
- db/migrate/20260713000100_solid_loop_add_execution_guards.rb
|
|
162
|
+
- db/migrate/20260714000100_solid_loop_add_message_steering.rb
|
|
163
|
+
- db/migrate/20260714000200_solid_loop_add_message_conversation_order.rb
|
|
164
|
+
- db/migrate/20260715000100_solid_loop_add_mcp_inbound_session_principal_key.rb
|
|
165
|
+
- db/migrate/20260715000200_solid_loop_add_llm_lease.rb
|
|
166
|
+
- db/migrate/20260715000300_solid_loop_add_tool_lease.rb
|
|
167
|
+
- db/migrate/20260715000400_solid_loop_add_lease_running_check.rb
|
|
168
|
+
- db/migrate/20260715000500_solid_loop_add_tool_lease_pair_check.rb
|
|
169
|
+
- docs/contributing/coverage.md
|
|
170
|
+
- docs/decisions/durable_attempt_lease.md
|
|
171
|
+
- docs/decisions/mcp-only-tooling.md
|
|
172
|
+
- docs/decisions/mcp-server.md
|
|
173
|
+
- docs/decisions/reasoning_persistence.md
|
|
174
|
+
- docs/decisions/ruby_llm_rejected.md
|
|
175
|
+
- docs/guides/dialects.md
|
|
176
|
+
- docs/guides/llm_middlewares.md
|
|
177
|
+
- docs/guides/mcp_transports.md
|
|
178
|
+
- docs/guides/tool_middlewares.md
|
|
179
|
+
- lib/solid_loop.rb
|
|
180
|
+
- lib/solid_loop/configuration.rb
|
|
181
|
+
- lib/solid_loop/engine.rb
|
|
182
|
+
- lib/solid_loop/lease_heartbeat.rb
|
|
183
|
+
- lib/solid_loop/lease_renewer.rb
|
|
184
|
+
- lib/solid_loop/llm_metrics.rb
|
|
185
|
+
- lib/solid_loop/mcp.rb
|
|
186
|
+
- lib/solid_loop/mcp/call_context.rb
|
|
187
|
+
- lib/solid_loop/mcp/client_factory.rb
|
|
188
|
+
- lib/solid_loop/mcp/http_transport.rb
|
|
189
|
+
- lib/solid_loop/mcp/principal.rb
|
|
190
|
+
- lib/solid_loop/mcp/result.rb
|
|
191
|
+
- lib/solid_loop/mcp/server.rb
|
|
192
|
+
- lib/solid_loop/mcp/stdio_transport.rb
|
|
193
|
+
- lib/solid_loop/mcp/toolset.rb
|
|
194
|
+
- lib/solid_loop/mcp/transport.rb
|
|
195
|
+
- lib/solid_loop/mcp_client.rb
|
|
196
|
+
- lib/solid_loop/pipeline.rb
|
|
197
|
+
- lib/solid_loop/pipeline/builder.rb
|
|
198
|
+
- lib/solid_loop/pipeline/context.rb
|
|
199
|
+
- lib/solid_loop/pipeline/tool_context.rb
|
|
200
|
+
- lib/solid_loop/reaper.rb
|
|
201
|
+
- lib/solid_loop/version.rb
|
|
202
|
+
- lib/tasks/coverage.rake
|
|
203
|
+
- lib/tasks/solid_loop_tasks.rake
|
|
204
|
+
homepage: https://github.com/ruslan/solid_loop
|
|
205
|
+
licenses:
|
|
206
|
+
- MIT
|
|
207
|
+
metadata:
|
|
208
|
+
source_code_uri: https://github.com/ruslan/solid_loop
|
|
209
|
+
changelog_uri: https://github.com/ruslan/solid_loop/blob/main/CHANGELOG.md
|
|
210
|
+
rubygems_mfa_required: 'true'
|
|
211
|
+
rdoc_options: []
|
|
212
|
+
require_paths:
|
|
213
|
+
- lib
|
|
214
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
215
|
+
requirements:
|
|
216
|
+
- - ">="
|
|
217
|
+
- !ruby/object:Gem::Version
|
|
218
|
+
version: '3.1'
|
|
219
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
220
|
+
requirements:
|
|
221
|
+
- - ">="
|
|
222
|
+
- !ruby/object:Gem::Version
|
|
223
|
+
version: '0'
|
|
224
|
+
requirements: []
|
|
225
|
+
rubygems_version: 3.6.7
|
|
226
|
+
specification_version: 4
|
|
227
|
+
summary: ActiveJob-backed infrastructure for autonomous AI agents in Rails.
|
|
228
|
+
test_files: []
|