robot_lab-rails 0.2.1 → 0.2.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aea941ab401ce28705914a98694a577399c78cff4cbebb6513b6a3fba02a4525
4
- data.tar.gz: aeb31e16dec1408a62bd4fb3424e2b09b7096f845084e6a7ed664ccc253cac29
3
+ metadata.gz: 363024a9b53fc93e6bee9f45d93ade96b62f8e2fcc86172eb1233baea141b525
4
+ data.tar.gz: 3ad6bf319772440866cb90ac41169e99eff28e9137143281685c48e46d652774
5
5
  SHA512:
6
- metadata.gz: e5e0201b324dc571199341a6616a22ac18fe477b0c70ac2c86a25614360f8e87074c6142646baba92e9562442c03a42155f9368a5d9bea060e508182b9c4fb5f
7
- data.tar.gz: 57b6d986ba10d5c08eee1ea4f0ef97dccfde83b5bccf558cbba62dde9389a899fc532f9890a0c0a65c5fc4d1e2adb5320383e44f58f11eccc699ebc2c757455e
6
+ metadata.gz: ec1fcbe29db8f114c0ce38a942a1582d1d018ac0a834412368763cb02d4bd5e3886cfa55da2d914d2408e7dca553bc1c86c63512f4f717bcb7e8a8dafa1b4ba2
7
+ data.tar.gz: 5332b0bd26088e36c01610ceae13b34ca3850c3a58a91a82a4fac264239f3dfae6b9625ace7c0f495e91e7d60d31c9b1ac98ce80aa561131d27021a35725d0fe
data/.envrc CHANGED
@@ -1 +1,3 @@
1
+ source_up
1
2
  export RR=`pwd`
3
+ export BUNDLE_GEMFILE=Gemfile
data/.loki ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ # robot_lab-rails — ActiveJob, generators, Turbo broadcasts for Rails apps.
3
+
4
+ import_up "repo_dev.loki"
5
+
6
+ class Tasks
7
+ @@gem_name ||= "robot_lab-rails".freeze
8
+
9
+ header "robot_lab-rails v#{gem_version} — Rails integration for RobotLab"
10
+
11
+ desc "Run Rails-specific integration tests"
12
+ def integration
13
+ sh "bundle exec rake test"
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ 2 lib/generators/robot_lab/install_generator.rb
2
+ 1 lib/robot_lab/rails_integration/engine.rb
3
+ 3 lib/robot_lab/rails_integration/job.rb
data/.rubocop.yml CHANGED
@@ -1,173 +1 @@
1
- AllCops:
2
- NewCops: enable
3
- SuggestExtensions: false
4
- TargetRubyVersion: 4.0
5
- Exclude:
6
- - 'examples/**/*'
7
- - 'vendor/**/*'
8
- - 'dead_code/**/*'
9
-
10
- # ── Style: disabled cops ───────────────────────────────────────────────────
11
- Style/StringLiterals:
12
- Enabled: false
13
-
14
- Style/StringLiteralsInInterpolation:
15
- Enabled: false
16
-
17
- Style/Documentation:
18
- Enabled: false
19
-
20
- # Ruby 4.0 freezes string literals by default
21
- Style/FrozenStringLiteralComment:
22
- Enabled: false
23
-
24
- Style/IfUnlessModifier:
25
- Enabled: false
26
-
27
- Style/RescueModifier:
28
- Enabled: false
29
-
30
- Style/TrivialAccessors:
31
- Enabled: false
32
-
33
- Style/MultilineTernaryOperator:
34
- Enabled: false
35
-
36
- Style/SafeNavigation:
37
- Enabled: false
38
-
39
- Style/EmptyClassDefinition:
40
- Enabled: false
41
-
42
- Style/ClassAndModuleChildren:
43
- Enabled: false
44
-
45
- Style/RescueStandardError:
46
- Enabled: false
47
-
48
- Style/OneClassPerFile:
49
- Enabled: false
50
-
51
- # Both % and format/sprintf are acceptable
52
- Style/FormatString:
53
- Enabled: false
54
-
55
- # String concatenation and interpolation are both acceptable
56
- Style/StringConcatenation:
57
- Enabled: false
58
-
59
- # ── Layout ─────────────────────────────────────────────────────────────────
60
- Layout/LineLength:
61
- Max: 140
62
-
63
- Layout/ExtraSpacing:
64
- Enabled: false
65
-
66
- Layout/HashAlignment:
67
- Enabled: false
68
-
69
- Layout/FirstHashElementIndentation:
70
- Enabled: false
71
-
72
- Layout/EmptyLineAfterGuardClause:
73
- Enabled: false
74
-
75
- # ── Naming ─────────────────────────────────────────────────────────────────
76
- # Single-char params (c, e, n) are acceptable throughout
77
- Naming/MethodParameterName:
78
- Enabled: false
79
-
80
- Naming/VariableNumber:
81
- Exclude:
82
- - 'test/**/*'
83
-
84
- Naming/RescuedExceptionsVariableName:
85
- Enabled: false
86
-
87
- # set_results and similar explicit setters are clear and conventional
88
- Naming/AccessorMethodName:
89
- Enabled: false
90
-
91
-
92
- # has_tool_calls? and similar are clear and conventional
93
- Naming/PredicatePrefix:
94
- Enabled: false
95
-
96
- # Test helper methods don't need to follow predicate naming rules
97
- Naming/PredicateMethod:
98
- Exclude:
99
- - 'test/**/*'
100
-
101
- # ── Lint: relax noisy cops on intentional patterns ─────────────────────────
102
- # Library and framework methods commonly accept args for API/documentation purposes
103
- Lint/UnusedMethodArgument:
104
- Enabled: false
105
-
106
-
107
- Lint/EmptyBlock:
108
- Exclude:
109
- - 'test/**/*'
110
-
111
- Lint/ConstantDefinitionInBlock:
112
- Exclude:
113
- - 'Rakefile'
114
- - 'test/**/*'
115
-
116
- # ── Gemspec ────────────────────────────────────────────────────────────────
117
- Gemspec/DevelopmentDependencies:
118
- EnforcedStyle: Gemfile
119
-
120
- Gemspec/RequiredRubyVersion:
121
- Enabled: false
122
-
123
- Gemspec/OrderedDependencies:
124
- Enabled: false
125
-
126
- # ── Metrics ────────────────────────────────────────────────────────────────
127
- # Framework-level code (routers, parsers, orchestrators) is inherently complex.
128
- # Flog is the primary complexity gate — these RuboCop thresholds catch only
129
- # egregious outliers without false-positiving every dispatch method.
130
-
131
- Metrics/MethodLength:
132
- Max: 35
133
- CountAsOne:
134
- - heredoc
135
- - array
136
- - hash
137
- Exclude:
138
- - 'test/**/*'
139
-
140
- Metrics/AbcSize:
141
- Max: 40
142
- Exclude:
143
- - 'test/**/*'
144
-
145
- Metrics/ClassLength:
146
- Max: 600
147
- Exclude:
148
- - 'test/**/*'
149
-
150
- Metrics/ModuleLength:
151
- Max: 200
152
- Exclude:
153
- - 'test/**/*'
154
-
155
- Metrics/CyclomaticComplexity:
156
- Max: 20
157
- Exclude:
158
- - 'test/**/*'
159
-
160
- Metrics/PerceivedComplexity:
161
- Max: 20
162
- Exclude:
163
- - 'test/**/*'
164
-
165
- # Long method signatures with keyword args are a Ruby framework idiom
166
- Metrics/ParameterLists:
167
- Enabled: false
168
-
169
- Metrics/BlockLength:
170
- Exclude:
171
- - 'Rakefile'
172
- - '*.gemspec'
173
- - 'test/**/*'
1
+ inherit_from: ../.rubocop-base.yml
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ### Added
4
+ - `.loki` Asgard task file: `test`, `rubocop`, `rubocop_fix`, `flog`, `flay`, `quality`, `build`, `install`, `release`, and `console` tasks via the Asgard task runner
5
+ - `flay_check` Rake task: structural code duplication gate (mass threshold 50); integrated into the `quality` Rake task
6
+ - `flay` and `minitest-reporters` gems added to development dependencies
7
+ - `test_output.txt`, `flay_output.txt`, `flog_output.txt`, and `rubocop_output.txt` added to `.gitignore`
8
+
9
+ ### Changed
10
+ - `test/test_helper.rb`: test output redirected to `test_output.txt` via `$stdout` reassignment; `TerminalSummaryReporter` prints a single PASS/FAIL summary line to the terminal
11
+ - `Rakefile`: `rubocop` and `rubocop_fix` tasks removed (now owned by Asgard); `flay_check` integrated into the `quality` gate
12
+
3
13
  ## [0.2.1] - 2026-05-19
4
14
 
5
15
  ### Added
data/CLAUDE.md ADDED
@@ -0,0 +1,71 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What This Gem Does
6
+
7
+ `robot_lab-rails` is a Rails Engine that integrates RobotLab into Rails applications. It provides a Railtie for configuration, an ActiveJob base class with optional Turbo Stream broadcasting, generators for scaffolding, and autoloading for `app/robots/` and `app/tools/`.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ bundle exec rake test # Run full test suite
13
+ ruby -Ilib:test test/<file> # Run a single test file
14
+
15
+ # Inside a Rails app using this gem:
16
+ rails g robot_lab:install # migration, models, initializer, job
17
+ rails g robot_lab:robot MyRobot # app/robots/my_robot.rb + test
18
+ rails g robot_lab:robot MyRouter --routing # routing robot variant
19
+ rails g robot_lab:job MyRobot # app/jobs/my_robot_job.rb
20
+ ```
21
+
22
+ ## Architecture
23
+
24
+ **`RailsIntegration::Engine`** (`rails_integration/engine.rb`) — Rails Engine with `isolate_namespace RobotLab`. Adds `app/robots/` and `app/tools/` to Rails autoload paths so robot and tool classes are autoloaded like models.
25
+
26
+ **`RailsIntegration::Railtie`** (`rails_integration/railtie.rb`) — Reads `config.robot_lab.default_model` and `config.robot_lab.default_provider` from the Rails app config and applies them to `RobotLab.configure`. Sets `RobotLab.config.logger = Rails.logger` so all RobotLab log output goes through Rails logging.
27
+
28
+ **`RailsIntegration::Job`** (`rails_integration/job.rb`) — `ActiveJob::Base` subclass. Subclass with `robot_class MyRobot` DSL, or pass `robot_class:` at enqueue time.
29
+
30
+ ```ruby
31
+ class SupportRobotJob < RobotLab::Job
32
+ queue_as :default
33
+ robot_class SupportRobot
34
+ end
35
+
36
+ SupportRobotJob.perform_later(message: "Hello", thread_id: session_id)
37
+ ```
38
+
39
+ When `thread_id` is provided and Turbo is available, the job streams content and tool-call badges live via `Turbo::StreamsChannel`, and persists the result to `RobotLabThread` / `RobotLabResult` ActiveRecord models. Built-in retry (3 attempts, 5 second wait) and discard on `DeserializationError`.
40
+
41
+ **`RailsIntegration::TurboStreamCallbacks`** (`rails_integration/turbo_stream_callbacks.rb`) — Stateless module. Safe to require without turbo-rails installed (guards with `defined?(Turbo::StreamsChannel)` at call time).
42
+
43
+ ```ruby
44
+ cb = TurboStreamCallbacks.build_content_callback(stream_name: "robot_lab_thread_42")
45
+ # => Proc that broadcasts each content chunk as HTML-escaped text
46
+
47
+ tool_cb = TurboStreamCallbacks.build_tool_call_callback(stream_name: "robot_lab_thread_42")
48
+ # => Proc that broadcasts a tool-badge span per tool call
49
+ ```
50
+
51
+ ## Generator Templates
52
+
53
+ Templates live in `lib/generators/robot_lab/templates/`. The install generator creates:
54
+ - `config/initializers/robot_lab.rb`
55
+ - `db/migrate/create_robot_lab_tables.rb` (RobotLabThread + RobotLabResult tables)
56
+ - `app/models/robot_lab_thread.rb` / `app/models/robot_lab_result.rb`
57
+ - `app/jobs/robot_run_job.rb`
58
+ - Empty `app/robots/` and `app/tools/` directories
59
+
60
+ ## Key Constraints
61
+
62
+ - `Engine` and `Railtie` are only loaded when `defined?(Rails)` — the gem is safe to require in non-Rails contexts (e.g. test helpers that stub Rails).
63
+ - `Job#setup_thread` calls `"RobotLabThread".constantize` — this is a string reference to avoid a hard dependency; ensure the model is generated before using `thread_id`.
64
+ - `TurboStreamCallbacks` HTML-escapes all content via `ERB::Util.html_escape` — do not bypass this.
65
+ - The Railtie uses `ActiveSupport.on_load(:active_record)` for AR setup — place any ActiveRecord extensions there, not in the initializer.
66
+
67
+ ## Testing
68
+
69
+ - Minitest with SimpleCov (branch coverage tracked, no minimum threshold enforced yet)
70
+ - Tests stub Rails, ActiveJob, and Turbo — no Rails app is loaded
71
+ - Coverage baseline: ~80% line / ~82% branch
data/README.md CHANGED
@@ -78,6 +78,31 @@ SupportJob.perform_later(message: params[:message], thread_id: session_id)
78
78
 
79
79
  Omitting `thread_id` runs the robot in fire-and-forget mode — no persistence, no broadcasting.
80
80
 
81
+ ## Recurring Tasks (Solid Queue)
82
+
83
+ When using [Solid Queue](https://github.com/rails/solid_queue), skip the wrapper job and call a class method directly via the `command:` key in `config/recurring.yml`:
84
+
85
+ ```ruby
86
+ # app/services/daily_digest.rb
87
+ class DailyDigest
88
+ def self.run
89
+ User.digest_subscribers.find_each do |user|
90
+ DailyDigestJob.perform_later(user_id: user.id)
91
+ end
92
+ end
93
+ end
94
+ ```
95
+
96
+ ```yaml
97
+ # config/recurring.yml
98
+ daily_digest:
99
+ command: "DailyDigest.run"
100
+ schedule: "every day at 06:00"
101
+ queue: ai # optional; defaults to solid_queue_recurring
102
+ ```
103
+
104
+ The orchestrator class is independently testable. For overnight autonomous runs, `RobotLab::To.run` works as a recurring command directly. See the [Rails Integration Guide](https://madbomber.github.io/robot_lab-rails/guides/rails-integration#recurring-tasks-solid-queue) for queue configuration details and the `robot_lab-to` pattern.
105
+
81
106
  ## Turbo Stream Streaming
82
107
 
83
108
  When `thread_id` is provided and [turbo-rails](https://github.com/hotwired/turbo-rails) is installed, `RobotLab::Job` automatically:
data/Rakefile CHANGED
@@ -24,16 +24,6 @@ task :test_file, [:file] do |_t, args|
24
24
  ruby "test/#{args[:file]}"
25
25
  end
26
26
 
27
- desc 'Check code style with RuboCop'
28
- task :rubocop do
29
- sh 'bundle exec rubocop'
30
- end
31
-
32
- desc 'Auto-correct RuboCop offenses'
33
- task :rubocop_fix do
34
- sh 'bundle exec rubocop -a'
35
- end
36
-
37
27
  desc 'Check code complexity with Flog (warn >=20, fail >=50)'
38
28
  task :flog_check do
39
29
  require 'flog'
@@ -71,36 +61,62 @@ task :flog_check do
71
61
  end
72
62
  end
73
63
 
74
- desc 'Run all quality checks: tests (with coverage), RuboCop, and Flog'
75
- task :quality do
76
- results = {}
64
+ desc 'Check for structural code duplication with Flay (mass >= 50)'
65
+ task :flay_check do
66
+ require 'flay'
77
67
 
78
- puts "\n#{'=' * 60}"
79
- puts 'Quality Gate: Tests + Coverage'
80
- puts '=' * 60
81
- results[:tests] = system('bundle exec rake test') ? :pass : :fail
68
+ mass_threshold = 50
82
69
 
83
- puts "\n#{'=' * 60}"
84
- puts 'Quality Gate: RuboCop'
85
- puts '=' * 60
86
- results[:rubocop] = system('bundle exec rubocop') ? :pass : :fail
70
+ flay = Flay.new({ mass: mass_threshold, diff: false, verbose: false, summary: false, timeout: 60 })
71
+ flay.process(*Dir.glob('lib/**/*.rb'))
72
+ flay.analyze
87
73
 
88
- puts "\n#{'=' * 60}"
89
- puts 'Quality Gate: Flog Complexity'
90
- puts '=' * 60
91
- results[:flog] = system('bundle exec rake flog_check') ? :pass : :fail
74
+ if flay.hashes.empty?
75
+ puts "\nFlay: no structural duplication detected (mass >= #{mass_threshold})"
76
+ else
77
+ puts "\nFlay found structural duplication (mass >= #{mass_threshold}):"
78
+ flay.report
79
+ abort "\nFlay quality gate failed: #{flay.hashes.length} pattern(s) detected"
80
+ end
81
+ end
82
+
83
+ desc 'Run all quality checks: tests (with coverage), RuboCop, Flog, and Flay'
84
+ task :quality do
85
+ gates = [
86
+ ['Tests + Coverage', 'bundle exec rake test'],
87
+ ['RuboCop', 'bundle exec rubocop'],
88
+ ['Flog Complexity', 'bundle exec rake flog_check'],
89
+ ['Flay Duplication', 'bundle exec rake flay_check']
90
+ ]
91
+
92
+ results = gates.map do |label, command|
93
+ puts "\n#{'=' * 60}"
94
+ puts "Quality Gate: #{label}"
95
+ puts '=' * 60
96
+ [label, system(command) ? :pass : :fail]
97
+ end
98
+
99
+ green = ->(s) { "\e[32m#{s}\e[0m" }
100
+ red = ->(s) { "\e[31m#{s}\e[0m" }
101
+ width = results.map { |label, _| label.length }.max
92
102
 
93
103
  puts "\n#{'=' * 60}"
94
- puts 'Quality Summary'
104
+ puts 'Quality Gate Summary'
95
105
  puts '=' * 60
96
- results.each do |gate, status|
97
- icon = status == :pass ? 'PASS' : 'FAIL'
98
- puts " [#{icon}] #{gate}"
106
+ results.each do |label, status|
107
+ badge = status == :pass ? green.call('PASS') : red.call('FAIL')
108
+ puts " [#{badge}] #{label.ljust(width)}"
99
109
  end
110
+ puts '-' * 60
111
+
112
+ passed = results.count { |_, s| s == :pass }
113
+ failed = results.count { |_, s| s == :fail }
114
+ tally = "#{passed} passed, #{failed} failed"
115
+ puts " #{failed.zero? ? green.call(tally) : red.call(tally)}"
100
116
  puts '=' * 60
101
117
 
102
- abort "\nQuality gate failed" if results.values.any?(:fail)
103
- puts "\nAll quality gates passed."
118
+ abort "\n#{red.call('Quality gate failed.')}" unless failed.zero?
119
+ puts "\n#{green.call('All quality gates passed.')}"
104
120
  end
105
121
 
106
122
  namespace :docs do
@@ -121,8 +121,15 @@ RobotLab.config.streaming_enabled #=> true
121
121
  rails generate robot_lab:robot Support
122
122
  rails generate robot_lab:robot Billing --description="Handles billing inquiries"
123
123
  rails generate robot_lab:robot Router --routing
124
+ rails generate robot_lab:robot Support --tools=order_lookup,refund_processor
124
125
  ```
125
126
 
127
+ Options:
128
+
129
+ - `--description` — Robot description (defaults to `"A helpful <Name> robot"`)
130
+ - `--routing` — Generate a routing robot (subclasses `RobotLab::Robot`, overrides `call`) instead of the plain factory-style robot
131
+ - `--tools` — List of tool names; emitted as commented-out entries in the generated `tools` array for you to uncomment and wire up
132
+
126
133
  ### Robot Class
127
134
 
128
135
  Robots are plain Ruby classes with a `.build` factory method that calls `RobotLab.build` with keyword arguments:
@@ -503,6 +510,83 @@ class ProcessMessageJob < ApplicationJob
503
510
  end
504
511
  ```
505
512
 
513
+ ## Recurring Tasks (Solid Queue)
514
+
515
+ When using [Solid Queue](https://github.com/rails/solid_queue), you can schedule robot runs without writing a thin wrapper job. Solid Queue's `command:` key in `config/recurring.yml` calls a class method directly, eliminating unnecessary glue code.
516
+
517
+ ### Orchestrator class
518
+
519
+ Keep the scheduling logic in a plain Ruby class. The class method fans out per-item jobs — or runs a robot directly:
520
+
521
+ ```ruby title="app/services/daily_digest.rb"
522
+ class DailyDigest
523
+ def self.run
524
+ User.digest_subscribers.find_each do |user|
525
+ DailyDigestJob.perform_later(user_id: user.id)
526
+ end
527
+ end
528
+ end
529
+ ```
530
+
531
+ ### Wiring in recurring.yml
532
+
533
+ Reference the method directly in `config/recurring.yml`:
534
+
535
+ ```yaml title="config/recurring.yml"
536
+ daily_digest:
537
+ command: "DailyDigest.run"
538
+ schedule: "every day at 06:00"
539
+ ```
540
+
541
+ No wrapper job needed. `DailyDigest.run` is a plain class method — independently testable without enqueuing anything.
542
+
543
+ ### Queue configuration
544
+
545
+ Solid Queue sends recurring commands to the `solid_queue_recurring` queue by default. Add a dedicated worker in `config/queue.yml` so recurring runs don't compete with your main job queues:
546
+
547
+ ```yaml title="config/queue.yml"
548
+ production:
549
+ workers:
550
+ - queues: [solid_queue_recurring]
551
+ threads: 1
552
+ - queues: [default, ai]
553
+ threads: 3
554
+ ```
555
+
556
+ Override the queue per task with `queue:` to route recurring robot runs through a shared worker:
557
+
558
+ ```yaml title="config/recurring.yml"
559
+ daily_digest:
560
+ command: "DailyDigest.run"
561
+ schedule: "every day at 06:00"
562
+ queue: ai
563
+ ```
564
+
565
+ ### Using robot_lab-to for overnight runs
566
+
567
+ The `robot_lab-to` gem's `RobotLab::To.run` is designed for a single autonomous session, making it a natural fit for a Solid Queue recurring task:
568
+
569
+ ```ruby title="app/services/nightly_analysis.rb"
570
+ class NightlyAnalysis
571
+ def self.run
572
+ RobotLab::To.run(
573
+ "Analyze yesterday's support tickets and generate a quality report",
574
+ max_iterations: 10,
575
+ stop_when: "Report is written to disk"
576
+ )
577
+ end
578
+ end
579
+ ```
580
+
581
+ ```yaml title="config/recurring.yml"
582
+ nightly_analysis:
583
+ command: "NightlyAnalysis.run"
584
+ schedule: "every day at 02:00"
585
+ queue: ai
586
+ ```
587
+
588
+ > `RobotLab::To.run` is synchronous. Solid Queue runs it inside a worker process — this is intentional. Each nightly run blocks its worker for the duration of the autonomous loop.
589
+
506
590
  ## Testing
507
591
 
508
592
  ### Test Configuration
@@ -582,6 +666,10 @@ class RobotLabThread < ApplicationRecord
582
666
  def last_result
583
667
  results.order(sequence_number: :desc).first
584
668
  end
669
+
670
+ def message_count
671
+ results.count
672
+ end
585
673
  end
586
674
  ```
587
675
 
@@ -596,14 +684,24 @@ class RobotLabResult < ApplicationRecord
596
684
 
597
685
  validates :session_id, presence: true
598
686
  validates :robot_name, presence: true
687
+ validates :sequence_number, presence: true,
688
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
599
689
 
600
690
  default_scope { order(sequence_number: :asc) }
601
691
 
692
+ def output_messages
693
+ (output_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) }
694
+ end
695
+
696
+ def tool_call_messages
697
+ (tool_calls_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) }
698
+ end
699
+
602
700
  def to_robot_result
603
701
  RobotLab::RobotResult.new(
604
702
  robot_name: robot_name,
605
- output: (output_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
606
- tool_calls: (tool_calls_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
703
+ output: output_messages,
704
+ tool_calls: tool_call_messages,
607
705
  stop_reason: stop_reason
608
706
  )
609
707
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RobotLab
4
4
  module Rails
5
- VERSION = "0.2.1"
5
+ VERSION = "0.2.6"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robot_lab-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -75,8 +75,11 @@ extra_rdoc_files: []
75
75
  files:
76
76
  - ".envrc"
77
77
  - ".github/workflows/deploy-github-pages.yml"
78
+ - ".loki"
79
+ - ".quality/reek_baseline.txt"
78
80
  - ".rubocop.yml"
79
81
  - CHANGELOG.md
82
+ - CLAUDE.md
80
83
  - LICENSE.txt
81
84
  - README.md
82
85
  - Rakefile
@@ -153,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
156
  - !ruby/object:Gem::Version
154
157
  version: '0'
155
158
  requirements: []
156
- rubygems_version: 4.0.11
159
+ rubygems_version: 4.0.17
157
160
  specification_version: 4
158
161
  summary: Rails integration for the robot_lab LLM agent framework
159
162
  test_files: []