robot_lab-rails 0.1.0 → 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: 93f8a8d257c9dfe599fde397b4be1cc979df53eed8d38a3adca987e493852311
4
- data.tar.gz: a41afb0b4132c1c245a10f44d3b5d9dd40ced45ad264e0a7e08c342cf9fb5711
3
+ metadata.gz: 363024a9b53fc93e6bee9f45d93ade96b62f8e2fcc86172eb1233baea141b525
4
+ data.tar.gz: 3ad6bf319772440866cb90ac41169e99eff28e9137143281685c48e46d652774
5
5
  SHA512:
6
- metadata.gz: d84d9c25a0379bca316619e0268c9f727228572c09048cb2717591acf145f1459c4292617f3ba836d756a9bd0f79b4922fdec43812cf3d111ab57d8dc591d945
7
- data.tar.gz: e908c4bee35578e1ecd313b126ea87b7cc3719eba286cbe01ed11000fdd65dc09b41ba49fc069da64137d783d8c19076ae5dac4dd5418b8aac7b8b89c32b31ad
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 ADDED
@@ -0,0 +1 @@
1
+ inherit_from: ../.rubocop-base.yml
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
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
+
13
+ ## [0.2.1] - 2026-05-19
14
+
15
+ ### Added
16
+ - Rails Engine — autoloads `app/robots/` and `app/tools/` in the host application via Zeitwerk
17
+ - Railtie — wires `RobotLab.config.logger` to `Rails.logger` on boot and applies Rails-specific defaults
18
+ - `RobotLab::Job` (`RobotLab::RailsIntegration::Job`) — ActiveJob base class handling robot-class resolution, `RobotLabThread` persistence, Turbo Stream wiring, and completion/error broadcasting
19
+ - `robot_class` DSL on `RobotLab::Job` subclasses — binds a dedicated job to a specific robot class without requiring `robot_class:` at enqueue time
20
+ - `TurboStreamCallbacks` — streams content tokens and tool-call badges to `#robot_response` and `#robot_tools` targets in real time; graceful no-op when `turbo-rails` is absent
21
+ - Generator `robot_lab:install` — creates initializer, migration, models (`RobotLabThread`, `RobotLabResult`), `RobotRunJob`, and `app/robots/` / `app/tools/` directories
22
+ - Generator `robot_lab:robot NAME` — generates a robot class stub in `app/robots/`
23
+ - Generator `robot_lab:job NAME [--queue QUEUE]` — generates a dedicated job subclass pre-bound to a robot class
24
+ - Default retry/discard policy on `RobotLab::Job`: `retry_on StandardError` (3 attempts, 5 s wait); `discard_on ActiveJob::DeserializationError`
25
+
26
+ ### Changed
27
+ - Version synchronized with robot_lab core 0.2.1
28
+
3
29
  ## [0.1.0] - 2026-05-07
4
30
 
5
31
  - Initial release
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
@@ -1,8 +1,132 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "bundler/gem_tasks"
4
- require "minitest/test_task"
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
5
 
6
- Minitest::TestTask.create
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb', 'test/**/test_*.rb'].exclude('**/*_helper.rb')
10
+ t.verbose = true
11
+ t.ruby_opts << '-rtest_helper'
12
+ end
7
13
 
8
14
  task default: :test
15
+
16
+ desc 'Run tests with verbose output'
17
+ task :test_verbose do
18
+ ENV['TESTOPTS'] = '--verbose'
19
+ Rake::Task[:test].invoke
20
+ end
21
+
22
+ desc 'Run a single test file'
23
+ task :test_file, [:file] do |_t, args|
24
+ ruby "test/#{args[:file]}"
25
+ end
26
+
27
+ desc 'Check code complexity with Flog (warn >=20, fail >=50)'
28
+ task :flog_check do
29
+ require 'flog'
30
+
31
+ method_warn = 20.0
32
+ method_fail = 50.0
33
+
34
+ flogger = Flog.new(all: true)
35
+ flogger.flog(*Dir.glob('lib/**/*.rb'))
36
+
37
+ warnings = []
38
+ failures = []
39
+
40
+ flogger.each_by_score do |method, score|
41
+ next if method.end_with?('#none')
42
+
43
+ if score > method_fail
44
+ failures << "#{format('%.1f', score)}: #{method}"
45
+ elsif score > method_warn
46
+ warnings << "#{format('%.1f', score)}: #{method}"
47
+ end
48
+ end
49
+
50
+ unless warnings.empty?
51
+ puts "\nFlog warnings (#{method_warn}–#{method_fail}) — target for future refactoring:"
52
+ warnings.each { |v| puts " #{v}" }
53
+ end
54
+
55
+ if failures.empty?
56
+ puts "\nFlog: no methods exceed the failure threshold (>=#{method_fail})"
57
+ else
58
+ puts "\nFlog failures (>=#{method_fail}) — must be refactored:"
59
+ failures.each { |v| puts " #{v}" }
60
+ abort "\nFlog quality gate failed: #{failures.size} method(s) exceed #{method_fail}"
61
+ end
62
+ end
63
+
64
+ desc 'Check for structural code duplication with Flay (mass >= 50)'
65
+ task :flay_check do
66
+ require 'flay'
67
+
68
+ mass_threshold = 50
69
+
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
73
+
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
102
+
103
+ puts "\n#{'=' * 60}"
104
+ puts 'Quality Gate Summary'
105
+ puts '=' * 60
106
+ results.each do |label, status|
107
+ badge = status == :pass ? green.call('PASS') : red.call('FAIL')
108
+ puts " [#{badge}] #{label.ljust(width)}"
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)}"
116
+ puts '=' * 60
117
+
118
+ abort "\n#{red.call('Quality gate failed.')}" unless failed.zero?
119
+ puts "\n#{green.call('All quality gates passed.')}"
120
+ end
121
+
122
+ namespace :docs do
123
+ desc 'Build MkDocs documentation'
124
+ task :build do
125
+ sh 'mkdocs build'
126
+ end
127
+
128
+ desc 'Serve MkDocs documentation locally on http://localhost:8000'
129
+ task :serve do
130
+ sh 'mkdocs serve'
131
+ end
132
+ end
@@ -35,7 +35,7 @@ rails db:migrate
35
35
 
36
36
  ## Configuration
37
37
 
38
- RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for configuration. There is no `RobotLab.configure` block. Instead, settings are loaded from YAML files and environment variables in the following priority order:
38
+ RobotLab uses [MywayConfig](https://github.com/madbomber/myway_config) for static configuration. Settings are loaded from YAML files and environment variables in the following priority order:
39
39
 
40
40
  1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
41
41
  2. **Environment-specific overrides** (development, test, production sections)
@@ -85,14 +85,21 @@ ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
85
85
 
86
86
  RobotLab also falls back to standard provider environment variables (e.g. `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`) when the prefixed versions are not set.
87
87
 
88
- ### Initializer (Logger Only)
88
+ ### Initializer
89
89
 
90
- The only runtime-writable config attribute is the logger. The generated initializer sets it to the Rails logger:
90
+ Use `RobotLab.configure` to set runtime attributes. The generated initializer wires the logger to `Rails.logger`:
91
91
 
92
92
  ```ruby title="config/initializers/robot_lab.rb"
93
93
  # frozen_string_literal: true
94
94
 
95
- # Set the RobotLab logger to use Rails.logger
95
+ RobotLab.configure do |c|
96
+ c.logger = Rails.logger
97
+ end
98
+ ```
99
+
100
+ You can also assign it directly if you prefer a one-liner:
101
+
102
+ ```ruby
96
103
  RobotLab.config.logger = Rails.logger
97
104
  ```
98
105
 
@@ -114,8 +121,15 @@ RobotLab.config.streaming_enabled #=> true
114
121
  rails generate robot_lab:robot Support
115
122
  rails generate robot_lab:robot Billing --description="Handles billing inquiries"
116
123
  rails generate robot_lab:robot Router --routing
124
+ rails generate robot_lab:robot Support --tools=order_lookup,refund_processor
117
125
  ```
118
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
+
119
133
  ### Robot Class
120
134
 
121
135
  Robots are plain Ruby classes with a `.build` factory method that calls `RobotLab.build` with keyword arguments:
@@ -496,6 +510,83 @@ class ProcessMessageJob < ApplicationJob
496
510
  end
497
511
  ```
498
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
+
499
590
  ## Testing
500
591
 
501
592
  ### Test Configuration
@@ -575,6 +666,10 @@ class RobotLabThread < ApplicationRecord
575
666
  def last_result
576
667
  results.order(sequence_number: :desc).first
577
668
  end
669
+
670
+ def message_count
671
+ results.count
672
+ end
578
673
  end
579
674
  ```
580
675
 
@@ -589,14 +684,24 @@ class RobotLabResult < ApplicationRecord
589
684
 
590
685
  validates :session_id, presence: true
591
686
  validates :robot_name, presence: true
687
+ validates :sequence_number, presence: true,
688
+ numericality: { only_integer: true, greater_than_or_equal_to: 0 }
592
689
 
593
690
  default_scope { order(sequence_number: :asc) }
594
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
+
595
700
  def to_robot_result
596
701
  RobotLab::RobotResult.new(
597
702
  robot_name: robot_name,
598
- output: (output_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
599
- tool_calls: (tool_calls_data || []).map { |d| RobotLab::Message.from_hash(d.symbolize_keys) },
703
+ output: output_messages,
704
+ tool_calls: tool_call_messages,
600
705
  stop_reason: stop_reason
601
706
  )
602
707
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RobotLab
4
4
  module Rails
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.6"
6
6
  end
7
7
  end
@@ -3,8 +3,12 @@
3
3
  require_relative "rails/version"
4
4
  require_relative "rails_integration/turbo_stream_callbacks"
5
5
 
6
- if defined?(::Rails)
6
+ if defined?(Rails)
7
7
  require_relative "rails_integration/engine"
8
8
  require_relative "rails_integration/railtie"
9
9
  require_relative "rails_integration/job"
10
10
  end
11
+
12
+ if defined?(RobotLab) && RobotLab.respond_to?(:register_extension)
13
+ RobotLab.register_extension(:rails, RobotLab::Rails)
14
+ end
@@ -39,9 +39,10 @@ module RobotLab
39
39
 
40
40
  def resolve_robot_class(runtime_class)
41
41
  klass = runtime_class || self.class.robot_class
42
- raise ArgumentError,
43
- "No robot class specified. Pass robot_class: to perform or set robot_class on the job class." \
44
- unless klass
42
+ unless klass
43
+ raise ArgumentError,
44
+ "No robot class specified. Pass robot_class: to perform or set robot_class on the job class."
45
+ end
45
46
 
46
47
  return klass if klass.is_a?(Class)
47
48
 
@@ -13,7 +13,7 @@ module RobotLab
13
13
  end
14
14
 
15
15
  def self.build_content_callback(stream_name:, target: "robot_response")
16
- ->(chunk) {
16
+ lambda { |chunk|
17
17
  content = chunk.respond_to?(:content) ? chunk.content : chunk.to_s
18
18
  return unless content && TurboStreamCallbacks.available?
19
19
 
@@ -26,7 +26,7 @@ module RobotLab
26
26
  end
27
27
 
28
28
  def self.build_tool_call_callback(stream_name:, target: "robot_tools")
29
- ->(tool_call) {
29
+ lambda { |tool_call|
30
30
  return unless TurboStreamCallbacks.available?
31
31
 
32
32
  name = tool_call.respond_to?(:name) ? tool_call.name : tool_call.to_s
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.1.0
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -13,16 +13,16 @@ dependencies:
13
13
  name: robot_lab
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - ">="
16
+ - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0'
18
+ version: 0.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
- - - ">="
23
+ - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0'
25
+ version: 0.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: railties
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -75,7 +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"
80
+ - ".rubocop.yml"
78
81
  - CHANGELOG.md
82
+ - CLAUDE.md
79
83
  - LICENSE.txt
80
84
  - README.md
81
85
  - Rakefile
@@ -152,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
156
  - !ruby/object:Gem::Version
153
157
  version: '0'
154
158
  requirements: []
155
- rubygems_version: 4.0.11
159
+ rubygems_version: 4.0.17
156
160
  specification_version: 4
157
161
  summary: Rails integration for the robot_lab LLM agent framework
158
162
  test_files: []