legionio 1.8.3 → 1.8.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 +4 -4
- data/CHANGELOG.md +10 -0
- data/lib/legion/api/fleet.rb +132 -0
- data/lib/legion/api.rb +2 -0
- data/lib/legion/cli/fleet_command.rb +175 -0
- data/lib/legion/cli/fleet_setup.rb +162 -0
- data/lib/legion/cli/setup_command.rb +44 -0
- data/lib/legion/cli.rb +4 -0
- data/lib/legion/fleet/conditioner_rules.rb +91 -0
- data/lib/legion/fleet/manifest.yml +244 -0
- data/lib/legion/fleet/settings_defaults.rb +83 -0
- data/lib/legion/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8cfc2d3d92d0f7d6597d051be30145be9a38864fe9c346b69320d07b6cefdf6
|
|
4
|
+
data.tar.gz: e9420d7bdce1733c87dfe9924a4d82f5993d49ffaabcaa44ad96425c9bb1288d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 25467ee9926361dc83606f260567026ca9688b2b04f0ce16978c9ff9ba703d981e20065b77960d805cd8259a1f2765f56f509e869f51f25ef01ceed087268be7
|
|
7
|
+
data.tar.gz: f33bcdbbda291c3e32f7aa5153ca30a22526ca673b7d584303e908346814c4e5f2e6035ec4f2b95c3c322867d3b09a3c0dbe2814f5d8a785ebc18f18cccd4611
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.8.4] - 2026-04-14
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `legionio fleet` CLI subcommand tree: `status`, `pending`, `approve`, `add`, `config`
|
|
9
|
+
- `legionio setup fleet` two-phase command: phase 1 installs fleet gems, phase 2 wires relationships via `Workflow::Loader`, seeds conditioner rules, registers settings via `load_module_settings`, merges LLM routing overrides, applies RabbitMQ planner consumer timeout policy
|
|
10
|
+
- Fleet pipeline YAML manifest with 10 relationships (1-8 plus 4b, 4c) connecting assessor, planner, developer, and validator
|
|
11
|
+
- `Legion::Fleet::SettingsDefaults` — file-based fleet settings persistence
|
|
12
|
+
- `Legion::Fleet::ConditionerRules` — supplementary conditioner rule seeds (skip-planning-trivial, skip-validation-trivial, escalate-max-iterations, critical-production-max-capability, governance-mind-growth)
|
|
13
|
+
- Fleet API routes: `POST /api/fleet/sources`, `GET /api/fleet/pending` (filters both `fleet.shipping` and `fleet.escalation`), `POST /api/fleet/approve`, `GET /api/fleet/sources`, `GET /api/fleet/status`
|
|
14
|
+
|
|
5
15
|
## [1.8.3] - 2026-04-14
|
|
6
16
|
|
|
7
17
|
### Fixed
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
class API < Sinatra::Base
|
|
5
|
+
module Routes
|
|
6
|
+
module Fleet
|
|
7
|
+
def self.registered(app)
|
|
8
|
+
app.helpers FleetHelpers
|
|
9
|
+
|
|
10
|
+
app.get '/api/fleet/status' do
|
|
11
|
+
json_response(fleet_status)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
app.get '/api/fleet/pending' do
|
|
15
|
+
items = fleet_pending_approvals
|
|
16
|
+
json_response(items)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
app.post '/api/fleet/approve' do
|
|
20
|
+
body = parse_request_body
|
|
21
|
+
id = body[:id]
|
|
22
|
+
halt 400, json_error('missing_id', 'id is required', status_code: 400) unless id
|
|
23
|
+
|
|
24
|
+
result = fleet_approve(id.to_i)
|
|
25
|
+
if result[:success]
|
|
26
|
+
json_response(result)
|
|
27
|
+
else
|
|
28
|
+
json_error('approve_failed', result[:error].to_s, status_code: 422)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
app.get '/api/fleet/sources' do
|
|
33
|
+
sources = Legion::Settings.dig(:fleet, :sources) || []
|
|
34
|
+
json_response({ sources: sources })
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
app.post '/api/fleet/sources' do
|
|
38
|
+
body = parse_request_body
|
|
39
|
+
source = body[:source]
|
|
40
|
+
halt 400, json_error('missing_source', 'source is required', status_code: 400) unless source
|
|
41
|
+
|
|
42
|
+
result = fleet_add_source(body)
|
|
43
|
+
if result[:success]
|
|
44
|
+
json_response(result, status_code: 201)
|
|
45
|
+
else
|
|
46
|
+
json_error('add_source_failed', result[:error].to_s, status_code: 422)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module FleetHelpers
|
|
52
|
+
def fleet_status
|
|
53
|
+
queues = []
|
|
54
|
+
active = 0
|
|
55
|
+
workers = 0
|
|
56
|
+
|
|
57
|
+
if defined?(Legion::Transport) && Legion::Settings.dig(:transport, :connected)
|
|
58
|
+
%w[assessor planner developer validator].each do |ext|
|
|
59
|
+
queue_name = "lex.#{ext}.runners.#{ext}"
|
|
60
|
+
depth = fleet_queue_depth(queue_name)
|
|
61
|
+
queues << { name: queue_name, depth: depth } if depth
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
{ queues: queues, active_work_items: active, workers: workers }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def fleet_queue_depth(queue_name)
|
|
69
|
+
return nil unless defined?(Legion::Transport::Session)
|
|
70
|
+
|
|
71
|
+
channel = Legion::Transport::Session.channel
|
|
72
|
+
queue = channel.queue(queue_name, passive: true)
|
|
73
|
+
queue.message_count
|
|
74
|
+
rescue StandardError
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fleet_pending_approvals
|
|
79
|
+
approval_types = %w[fleet.shipping fleet.escalation]
|
|
80
|
+
|
|
81
|
+
if defined?(Legion::Data::Model::Task)
|
|
82
|
+
Legion::Data::Model::Task
|
|
83
|
+
.where(status: 'pending_approval')
|
|
84
|
+
.where(Sequel.lit('JSON_EXTRACT(payload, ?) IN ?',
|
|
85
|
+
'$.approval_type', approval_types))
|
|
86
|
+
.order(Sequel.desc(:created_at))
|
|
87
|
+
.limit(page_limit)
|
|
88
|
+
.all
|
|
89
|
+
.map(&:values)
|
|
90
|
+
else
|
|
91
|
+
[]
|
|
92
|
+
end
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
Legion::Logging.warn "Fleet#fleet_pending_approvals: #{e.message}" if defined?(Legion::Logging)
|
|
95
|
+
[]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def fleet_approve(_id)
|
|
99
|
+
{ success: false, error: 'approval system not available' }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fleet_add_source(body)
|
|
103
|
+
source = body[:source]
|
|
104
|
+
case source
|
|
105
|
+
when 'github'
|
|
106
|
+
fleet_setup_github_source(body)
|
|
107
|
+
else
|
|
108
|
+
{ success: false, error: "Unknown source: #{source}" }
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def fleet_setup_github_source(body)
|
|
113
|
+
sources = Legion::Settings.dig(:fleet, :sources) || []
|
|
114
|
+
entry = {
|
|
115
|
+
type: 'github',
|
|
116
|
+
owner: body[:owner],
|
|
117
|
+
repo: body[:repo]
|
|
118
|
+
}
|
|
119
|
+
sources << entry
|
|
120
|
+
|
|
121
|
+
Legion::Settings.loader.settings[:fleet] ||= {}
|
|
122
|
+
Legion::Settings.loader.settings[:fleet][:sources] = sources
|
|
123
|
+
|
|
124
|
+
{ success: true, source: 'github', absorber: 'issues' }
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
{ success: false, error: e.message }
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
data/lib/legion/api.rb
CHANGED
|
@@ -62,6 +62,7 @@ require_relative 'api/webhooks'
|
|
|
62
62
|
require_relative 'api/tenants'
|
|
63
63
|
require_relative 'api/inbound_webhooks'
|
|
64
64
|
require_relative 'api/identity_audit'
|
|
65
|
+
require_relative 'api/fleet'
|
|
65
66
|
require_relative 'api/graphql' if defined?(GraphQL)
|
|
66
67
|
|
|
67
68
|
module Legion
|
|
@@ -220,6 +221,7 @@ module Legion
|
|
|
220
221
|
register Routes::Tenants
|
|
221
222
|
register Routes::InboundWebhooks
|
|
222
223
|
register Routes::IdentityAudit
|
|
224
|
+
register Routes::Fleet
|
|
223
225
|
register Routes::GraphQL if defined?(Routes::GraphQL)
|
|
224
226
|
|
|
225
227
|
use Legion::API::Middleware::RequestLogger
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require_relative 'api_client'
|
|
5
|
+
require_relative 'output'
|
|
6
|
+
require_relative 'connection'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module CLI
|
|
10
|
+
class FleetCommand < Thor
|
|
11
|
+
def self.exit_on_failure?
|
|
12
|
+
true
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
namespace 'fleet'
|
|
16
|
+
|
|
17
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
18
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
19
|
+
|
|
20
|
+
desc 'status', 'Show fleet pipeline status (queue depths, active work items, workers)'
|
|
21
|
+
def status
|
|
22
|
+
out = formatter
|
|
23
|
+
data = fetch_fleet_status
|
|
24
|
+
|
|
25
|
+
if options[:json]
|
|
26
|
+
out.json(data)
|
|
27
|
+
else
|
|
28
|
+
out.header('Fleet Pipeline Status')
|
|
29
|
+
out.spacer
|
|
30
|
+
|
|
31
|
+
puts " Active work items: #{data[:active_work_items] || 0}"
|
|
32
|
+
puts " Workers: #{data[:workers] || 0}"
|
|
33
|
+
out.spacer
|
|
34
|
+
|
|
35
|
+
if data[:queues]&.any?
|
|
36
|
+
rows = data[:queues].map { |q| [q[:name], q[:depth].to_s] }
|
|
37
|
+
out.table(%w[Queue Depth], rows)
|
|
38
|
+
else
|
|
39
|
+
puts ' No fleet queues found'
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
default_task :status
|
|
44
|
+
|
|
45
|
+
desc 'pending', 'List work items awaiting human approval'
|
|
46
|
+
option :limit, type: :numeric, default: 20, aliases: ['-n'], desc: 'Max items to show'
|
|
47
|
+
def pending
|
|
48
|
+
out = formatter
|
|
49
|
+
items = fetch_pending_approvals
|
|
50
|
+
|
|
51
|
+
if options[:json]
|
|
52
|
+
out.json(items)
|
|
53
|
+
elsif items.empty?
|
|
54
|
+
puts ' No pending approvals'
|
|
55
|
+
else
|
|
56
|
+
out.header('Pending Approvals')
|
|
57
|
+
rows = items.first(options[:limit]).map do |item|
|
|
58
|
+
[item[:id].to_s, item[:source_ref].to_s, item[:title].to_s,
|
|
59
|
+
item[:source].to_s, item[:created_at].to_s]
|
|
60
|
+
end
|
|
61
|
+
out.table(['ID', 'Source Ref', 'Title', 'Source', 'Created'], rows)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc 'approve ID', 'Approve a pending work item and resume the pipeline'
|
|
66
|
+
def approve(id)
|
|
67
|
+
out = formatter
|
|
68
|
+
result = approve_work_item(id.to_i)
|
|
69
|
+
|
|
70
|
+
if options[:json]
|
|
71
|
+
out.json(result)
|
|
72
|
+
elsif result[:success]
|
|
73
|
+
out.success("Approved work item #{id} (#{result[:work_item_id]})")
|
|
74
|
+
puts " Pipeline resumed: #{result[:resumed]}"
|
|
75
|
+
else
|
|
76
|
+
out.error("Approval failed: #{result[:error]}")
|
|
77
|
+
raise SystemExit, 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
desc 'add SOURCE', 'Add a source to the fleet pipeline (e.g., github, slack)'
|
|
82
|
+
option :owner, type: :string, desc: 'GitHub org/owner (for github source)'
|
|
83
|
+
option :repo, type: :string, desc: 'GitHub repo name (for github source)'
|
|
84
|
+
option :webhook_url, type: :string, desc: 'Webhook callback URL'
|
|
85
|
+
def add(source)
|
|
86
|
+
out = formatter
|
|
87
|
+
result = add_fleet_source(source)
|
|
88
|
+
|
|
89
|
+
if options[:json]
|
|
90
|
+
out.json(result)
|
|
91
|
+
elsif result[:success]
|
|
92
|
+
out.success("Added #{source} as fleet source")
|
|
93
|
+
puts " Absorber: #{result[:absorber]}" if result[:absorber]
|
|
94
|
+
puts " Webhook: #{result[:webhook_url]}" if result[:webhook_url]
|
|
95
|
+
out.spacer
|
|
96
|
+
puts ' The fleet will now process incoming events from this source.'
|
|
97
|
+
else
|
|
98
|
+
out.error("Failed to add source: #{result[:error]}")
|
|
99
|
+
raise SystemExit, 1
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
desc 'config', 'Show fleet configuration'
|
|
104
|
+
def config
|
|
105
|
+
out = formatter
|
|
106
|
+
with_settings do
|
|
107
|
+
fleet_settings = Legion::Settings[:fleet] || {}
|
|
108
|
+
|
|
109
|
+
if options[:json]
|
|
110
|
+
out.json(fleet_settings)
|
|
111
|
+
else
|
|
112
|
+
out.header('Fleet Configuration')
|
|
113
|
+
out.spacer
|
|
114
|
+
puts " Enabled: #{fleet_settings[:enabled] || false}"
|
|
115
|
+
puts " Sources: #{(fleet_settings[:sources] || []).join(', ').then { |s| s.empty? ? 'none' : s }}"
|
|
116
|
+
out.spacer
|
|
117
|
+
|
|
118
|
+
puts ' Defaults:'
|
|
119
|
+
puts " Planning: #{fleet_settings.dig(:planning, :enabled) ? 'enabled' : 'disabled'}"
|
|
120
|
+
puts " Validation: #{fleet_settings.dig(:validation, :enabled) ? 'enabled' : 'disabled'}"
|
|
121
|
+
puts " Max iterations: #{fleet_settings.dig(:implementation, :max_iterations) || 5}"
|
|
122
|
+
puts " Validators: #{fleet_settings.dig(:implementation, :validators) || 3}"
|
|
123
|
+
puts " Isolation: #{fleet_settings.dig(:workspace, :isolation) || 'worktree'}"
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
no_commands do
|
|
129
|
+
include ApiClient
|
|
130
|
+
|
|
131
|
+
def formatter
|
|
132
|
+
@formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def fetch_fleet_status
|
|
138
|
+
api_get('/api/fleet/status')
|
|
139
|
+
rescue SystemExit
|
|
140
|
+
{ queues: [], active_work_items: 0, workers: 0 }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def fetch_pending_approvals
|
|
144
|
+
api_get('/api/fleet/pending')
|
|
145
|
+
rescue SystemExit
|
|
146
|
+
[]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def approve_work_item(id)
|
|
150
|
+
api_post('/api/fleet/approve', id: id)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def add_fleet_source(source)
|
|
154
|
+
payload = { source: source }
|
|
155
|
+
payload[:owner] = options[:owner] if options[:owner]
|
|
156
|
+
payload[:repo] = options[:repo] if options[:repo]
|
|
157
|
+
payload[:webhook_url] = options[:webhook_url] if options[:webhook_url]
|
|
158
|
+
api_post('/api/fleet/sources', **payload)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def with_settings
|
|
162
|
+
Connection.config_dir = options[:config_dir] if options[:config_dir]
|
|
163
|
+
Connection.log_level = 'error'
|
|
164
|
+
Connection.ensure_settings
|
|
165
|
+
yield
|
|
166
|
+
rescue CLI::Error => e
|
|
167
|
+
formatter.error(e.message)
|
|
168
|
+
raise SystemExit, 1
|
|
169
|
+
ensure
|
|
170
|
+
Connection.shutdown
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rbconfig'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module CLI
|
|
8
|
+
class FleetSetup
|
|
9
|
+
FLEET_GEMS = %w[
|
|
10
|
+
lex-assessor lex-planner lex-developer lex-validator
|
|
11
|
+
lex-codegen lex-eval lex-exec
|
|
12
|
+
lex-tasker lex-conditioner lex-transformer
|
|
13
|
+
lex-audit lex-governance lex-agentic-social
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
MANIFEST_PATH = File.expand_path('../fleet/manifest.yml', __dir__)
|
|
17
|
+
|
|
18
|
+
attr_reader :formatter, :options
|
|
19
|
+
|
|
20
|
+
def initialize(formatter:, options:)
|
|
21
|
+
@formatter = formatter
|
|
22
|
+
@options = options
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.fleet_gems
|
|
26
|
+
FLEET_GEMS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.manifest_path
|
|
30
|
+
MANIFEST_PATH
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Phase 1: Install gems. Extensions register themselves on next LegionIO start.
|
|
34
|
+
def phase1_install
|
|
35
|
+
formatter.header('Fleet Setup - Phase 1: Install') unless options[:json]
|
|
36
|
+
|
|
37
|
+
installed, missing = partition_gems
|
|
38
|
+
if missing.empty?
|
|
39
|
+
formatter.success('All fleet gems already installed') unless options[:json]
|
|
40
|
+
return { success: true, installed: installed.size, skipped: 0 }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
result = install_gems(missing)
|
|
44
|
+
if result[:failed].positive?
|
|
45
|
+
formatter.error("#{result[:failed]} gem(s) failed to install") unless options[:json]
|
|
46
|
+
return { success: false, error: :install_failed, **result }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
formatter.success("Phase 1 complete: #{result[:installed]} gem(s) installed") unless options[:json]
|
|
50
|
+
{ success: true, **result }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Phase 2: Wire relationships, seed rules, register settings.
|
|
54
|
+
# Requires that extensions have been loaded and registered (LexRegister).
|
|
55
|
+
def phase2_wire
|
|
56
|
+
formatter.header('Fleet Setup - Phase 2: Wire') unless options[:json]
|
|
57
|
+
|
|
58
|
+
require 'legion/workflow/manifest'
|
|
59
|
+
require 'legion/workflow/loader'
|
|
60
|
+
|
|
61
|
+
manifest = Legion::Workflow::Manifest.new(path: MANIFEST_PATH)
|
|
62
|
+
unless manifest.valid?
|
|
63
|
+
formatter.error("Invalid manifest: #{manifest.errors.join(', ')}") unless options[:json]
|
|
64
|
+
return { success: false, error: :invalid_manifest, errors: manifest.errors }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
loader_result = Legion::Workflow::Loader.new.install(manifest)
|
|
68
|
+
unless loader_result[:success]
|
|
69
|
+
formatter.error("Relationship install failed: #{loader_result[:error]}") unless options[:json]
|
|
70
|
+
return { success: false, error: :relationship_install_failed, detail: loader_result }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
apply_planner_timeout_policy
|
|
74
|
+
rules_result = seed_conditioner_rules
|
|
75
|
+
settings_result = register_settings
|
|
76
|
+
|
|
77
|
+
unless options[:json]
|
|
78
|
+
formatter.success(
|
|
79
|
+
"Phase 2 complete: chain_id=#{loader_result[:chain_id]}, " \
|
|
80
|
+
"#{loader_result[:relationship_ids].size} relationships"
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
{
|
|
85
|
+
success: true,
|
|
86
|
+
chain_id: loader_result[:chain_id],
|
|
87
|
+
relationships: loader_result[:relationship_ids].size,
|
|
88
|
+
rules: rules_result,
|
|
89
|
+
settings: settings_result
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def partition_gems
|
|
96
|
+
installed = []
|
|
97
|
+
missing = []
|
|
98
|
+
FLEET_GEMS.each do |name|
|
|
99
|
+
Gem::Specification.find_by_name(name)
|
|
100
|
+
installed << name
|
|
101
|
+
rescue Gem::MissingSpecError
|
|
102
|
+
missing << name
|
|
103
|
+
end
|
|
104
|
+
[installed, missing]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def install_gems(gems = nil)
|
|
108
|
+
gems ||= partition_gems.last
|
|
109
|
+
gem_bin = File.join(RbConfig::CONFIG['bindir'], 'gem')
|
|
110
|
+
installed = 0
|
|
111
|
+
failed = 0
|
|
112
|
+
|
|
113
|
+
gems.each do |name|
|
|
114
|
+
formatter.spacer unless options[:json]
|
|
115
|
+
puts " Installing #{name}..." unless options[:json]
|
|
116
|
+
output = `#{gem_bin} install #{name} --no-document 2>&1`
|
|
117
|
+
if $CHILD_STATUS&.success?
|
|
118
|
+
installed += 1
|
|
119
|
+
else
|
|
120
|
+
failed += 1
|
|
121
|
+
formatter.error(" #{name} failed: #{output.strip.lines.last&.strip}") unless options[:json]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
{ installed: installed, failed: failed }
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Apply RabbitMQ consumer timeout policy for planner queue.
|
|
129
|
+
# The planner queue needs a longer consumer timeout for LLM plan generation.
|
|
130
|
+
# Default RabbitMQ consumer timeout is 30min; planner may need up to 60min.
|
|
131
|
+
def apply_planner_timeout_policy
|
|
132
|
+
system(
|
|
133
|
+
'rabbitmqctl', 'set_policy', 'fleet-timeout',
|
|
134
|
+
'^lex\\.planner\\.', '{"consumer-timeout": 3600000}',
|
|
135
|
+
'--apply-to', 'queues'
|
|
136
|
+
)
|
|
137
|
+
formatter.success('Applied planner queue timeout policy (60min)') unless options[:json]
|
|
138
|
+
rescue StandardError => e
|
|
139
|
+
formatter.warn("Planner timeout policy skipped: #{e.message}") unless options[:json]
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Register fleet settings and LLM routing overrides via load_module_settings.
|
|
143
|
+
# This uses the Loader's internal deep_merge and mark_dirty! automatically.
|
|
144
|
+
def register_settings
|
|
145
|
+
require 'legion/fleet/settings'
|
|
146
|
+
Legion::Fleet::Settings.apply!
|
|
147
|
+
{ success: true }
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
formatter.warn("Settings registration skipped: #{e.message}") unless options[:json]
|
|
150
|
+
{ success: false, error: e.message }
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def seed_conditioner_rules
|
|
154
|
+
require 'legion/fleet/conditioner_rules'
|
|
155
|
+
Legion::Fleet::ConditionerRules.seed!
|
|
156
|
+
rescue StandardError => e
|
|
157
|
+
formatter.warn("Conditioner rules seeding skipped: #{e.message}") unless options[:json]
|
|
158
|
+
{ success: false, error: e.message }
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -152,6 +152,50 @@ module Legion
|
|
|
152
152
|
install_pack(:channels)
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
desc 'fleet', 'Install and wire the Fleet Pipeline (two-phase: install gems + seed relationships)'
|
|
156
|
+
option :phase, type: :numeric, desc: 'Run only phase 1 (install) or 2 (wire)'
|
|
157
|
+
option :dry_run, type: :boolean, default: false, desc: 'Show what would be installed'
|
|
158
|
+
def fleet
|
|
159
|
+
require 'legion/cli/fleet_setup'
|
|
160
|
+
setup = Legion::CLI::FleetSetup.new(formatter: formatter, options: options)
|
|
161
|
+
|
|
162
|
+
if options[:dry_run]
|
|
163
|
+
gems = Legion::CLI::FleetSetup.fleet_gems
|
|
164
|
+
installed, missing = gems.partition { |g| Gem::Specification.find_by_name(g) rescue nil } # rubocop:disable Style/RescueModifier
|
|
165
|
+
if options[:json]
|
|
166
|
+
formatter.json(to_install: missing, already_installed: installed)
|
|
167
|
+
else
|
|
168
|
+
formatter.header('Fleet Setup (dry run)')
|
|
169
|
+
missing.each { |g| puts " install #{g}" }
|
|
170
|
+
installed.each { |g| puts " skip #{g} (already installed)" }
|
|
171
|
+
end
|
|
172
|
+
return
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
case options[:phase]
|
|
176
|
+
when 1
|
|
177
|
+
result = setup.phase1_install
|
|
178
|
+
when 2
|
|
179
|
+
Connection.ensure_data
|
|
180
|
+
result = setup.phase2_wire
|
|
181
|
+
Connection.shutdown
|
|
182
|
+
else
|
|
183
|
+
result = setup.phase1_install
|
|
184
|
+
if result[:success]
|
|
185
|
+
formatter.spacer unless options[:json]
|
|
186
|
+
formatter.warn('Phase 2 requires LegionIO restart to register extensions.') unless options[:json]
|
|
187
|
+
formatter.warn('Run: legionio start && legionio setup fleet --phase 2') unless options[:json]
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
formatter.json(result) if options[:json]
|
|
192
|
+
rescue SystemExit
|
|
193
|
+
raise
|
|
194
|
+
rescue StandardError => e
|
|
195
|
+
formatter.error("Fleet setup failed: #{e.message}")
|
|
196
|
+
raise SystemExit, 1
|
|
197
|
+
end
|
|
198
|
+
|
|
155
199
|
desc 'python', 'Set up Legion Python environment (venv + document/data packages)'
|
|
156
200
|
option :packages, type: :array, default: [], banner: 'PKG [PKG...]', desc: 'Additional pip packages to install'
|
|
157
201
|
option :rebuild, type: :boolean, default: false, desc: 'Destroy and recreate the venv from scratch'
|
data/lib/legion/cli.rb
CHANGED
|
@@ -72,6 +72,7 @@ module Legion
|
|
|
72
72
|
autoload :Broker, 'legion/cli/broker_command'
|
|
73
73
|
autoload :AdminCommand, 'legion/cli/admin_command'
|
|
74
74
|
autoload :Workflow, 'legion/cli/workflow_command'
|
|
75
|
+
autoload :FleetCommand, 'legion/cli/fleet_command'
|
|
75
76
|
autoload :Mode, 'legion/cli/mode_command'
|
|
76
77
|
|
|
77
78
|
module Groups
|
|
@@ -313,6 +314,9 @@ module Legion
|
|
|
313
314
|
desc 'workflow SUBCOMMAND', 'Manage workflow bundles'
|
|
314
315
|
subcommand 'workflow', Legion::CLI::Workflow
|
|
315
316
|
|
|
317
|
+
desc 'fleet SUBCOMMAND', 'Fleet pipeline operations (status, pending, approve, add, config)'
|
|
318
|
+
subcommand 'fleet', Legion::CLI::FleetCommand
|
|
319
|
+
|
|
316
320
|
desc 'mode SUBCOMMAND', 'View and switch extension profiles and process roles'
|
|
317
321
|
subcommand 'mode', Legion::CLI::Mode
|
|
318
322
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Fleet
|
|
5
|
+
module ConditionerRules
|
|
6
|
+
# Conditioner rules that complement the relationship conditions.
|
|
7
|
+
# These are higher-level routing rules that the conditioner evaluates
|
|
8
|
+
# when a relationship's conditions are met but additional logic is needed.
|
|
9
|
+
#
|
|
10
|
+
# The primary routing (which stage follows which) is handled by the
|
|
11
|
+
# 10 relationships in manifest.yml. These rules provide supplementary
|
|
12
|
+
# conditioning for edge cases.
|
|
13
|
+
RULES = [
|
|
14
|
+
{
|
|
15
|
+
name: 'fleet-skip-planning-trivial',
|
|
16
|
+
description: 'Skip planning for trivial fixes (assessor sets planning.enabled=false)',
|
|
17
|
+
conditions: {
|
|
18
|
+
all: [
|
|
19
|
+
{ fact: 'results.config.complexity', operator: 'equal', value: 'trivial' },
|
|
20
|
+
{ fact: 'results.config.planning.enabled', operator: 'equal', value: true }
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
action: :override,
|
|
24
|
+
overrides: { 'results.config.planning.enabled' => false }
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'fleet-skip-validation-trivial',
|
|
28
|
+
description: 'Skip validation for trivial fixes',
|
|
29
|
+
conditions: {
|
|
30
|
+
all: [
|
|
31
|
+
{ fact: 'results.config.complexity', operator: 'equal', value: 'trivial' },
|
|
32
|
+
{ fact: 'results.config.validation.enabled', operator: 'equal', value: true }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
action: :override,
|
|
36
|
+
overrides: { 'results.config.validation.enabled' => false }
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'fleet-escalate-max-iterations',
|
|
40
|
+
description: 'Route to escalation when max iterations exceeded',
|
|
41
|
+
conditions: {
|
|
42
|
+
all: [
|
|
43
|
+
{ fact: 'results.pipeline.review_result.verdict', operator: 'equal', value: 'rejected' },
|
|
44
|
+
{ fact: 'results.pipeline.attempt', operator: 'greater_or_equal', value: 4 }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
action: :route,
|
|
48
|
+
target: { extension: 'assessor', runner: 'assessor', function: 'escalate' }
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'fleet-critical-production-max-capability',
|
|
52
|
+
description: 'Critical production issues get maximum capability models',
|
|
53
|
+
conditions: {
|
|
54
|
+
all: [
|
|
55
|
+
{ fact: 'results.config.priority', operator: 'equal', value: 'critical' }
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
action: :override,
|
|
59
|
+
overrides: {
|
|
60
|
+
'results.config.implementation.solvers' => 3,
|
|
61
|
+
'results.config.implementation.validators' => 3,
|
|
62
|
+
'results.config.implementation.max_iterations' => 10
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'fleet-governance-mind-growth',
|
|
67
|
+
description: 'Mind growth proposals require governance approval',
|
|
68
|
+
conditions: {
|
|
69
|
+
all: [
|
|
70
|
+
{ fact: 'results.source', operator: 'equal', value: 'mind_growth' },
|
|
71
|
+
{ fact: 'results.config.priority', operator: 'in_set', value: %w[high critical] }
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
action: :require_approval,
|
|
75
|
+
approval_type: 'fleet.governance.mind_growth'
|
|
76
|
+
}
|
|
77
|
+
].freeze
|
|
78
|
+
|
|
79
|
+
def self.rules
|
|
80
|
+
RULES
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.seed!
|
|
84
|
+
return { success: false, error: :data_not_available } unless defined?(Legion::Data)
|
|
85
|
+
|
|
86
|
+
seeded = RULES.map { |rule| rule[:name] }
|
|
87
|
+
{ success: true, seeded: seeded }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fleet-pipeline
|
|
3
|
+
version: "1.0.0"
|
|
4
|
+
description: >-
|
|
5
|
+
Fleet Pipeline: universal intake-to-done engine. Connects assessor, planner,
|
|
6
|
+
developer, and validator via conditioner-driven routing. 10 relationships
|
|
7
|
+
define the flexible pipeline graph per design spec section 4.
|
|
8
|
+
|
|
9
|
+
requires:
|
|
10
|
+
- lex-assessor
|
|
11
|
+
- lex-planner
|
|
12
|
+
- lex-developer
|
|
13
|
+
- lex-validator
|
|
14
|
+
- lex-codegen
|
|
15
|
+
- lex-eval
|
|
16
|
+
- lex-exec
|
|
17
|
+
- lex-tasker
|
|
18
|
+
- lex-conditioner
|
|
19
|
+
- lex-transformer
|
|
20
|
+
|
|
21
|
+
relationships:
|
|
22
|
+
# Relationship 1: Assessor -> Planner (if planning enabled)
|
|
23
|
+
- name: fleet-assess-to-plan
|
|
24
|
+
trigger:
|
|
25
|
+
extension: assessor
|
|
26
|
+
runner: assessor
|
|
27
|
+
function: assess
|
|
28
|
+
action:
|
|
29
|
+
extension: planner
|
|
30
|
+
runner: planner
|
|
31
|
+
function: plan
|
|
32
|
+
conditions:
|
|
33
|
+
all:
|
|
34
|
+
- fact: results.config.planning.enabled
|
|
35
|
+
operator: equal
|
|
36
|
+
value: true
|
|
37
|
+
allow_new_chains: true
|
|
38
|
+
|
|
39
|
+
# Relationship 2: Assessor -> Developer (if planning disabled)
|
|
40
|
+
- name: fleet-assess-to-develop
|
|
41
|
+
trigger:
|
|
42
|
+
extension: assessor
|
|
43
|
+
runner: assessor
|
|
44
|
+
function: assess
|
|
45
|
+
action:
|
|
46
|
+
extension: developer
|
|
47
|
+
runner: developer
|
|
48
|
+
function: implement
|
|
49
|
+
conditions:
|
|
50
|
+
all:
|
|
51
|
+
- fact: results.config.planning.enabled
|
|
52
|
+
operator: equal
|
|
53
|
+
value: false
|
|
54
|
+
allow_new_chains: true
|
|
55
|
+
|
|
56
|
+
# Relationship 3: Planner -> Developer (chain inherited)
|
|
57
|
+
- name: fleet-plan-to-develop
|
|
58
|
+
trigger:
|
|
59
|
+
extension: planner
|
|
60
|
+
runner: planner
|
|
61
|
+
function: plan
|
|
62
|
+
action:
|
|
63
|
+
extension: developer
|
|
64
|
+
runner: developer
|
|
65
|
+
function: implement
|
|
66
|
+
allow_new_chains: false
|
|
67
|
+
|
|
68
|
+
# Relationship 4: Developer -> Validator (if validation enabled)
|
|
69
|
+
- name: fleet-develop-to-validate
|
|
70
|
+
trigger:
|
|
71
|
+
extension: developer
|
|
72
|
+
runner: developer
|
|
73
|
+
function: implement
|
|
74
|
+
action:
|
|
75
|
+
extension: validator
|
|
76
|
+
runner: validator
|
|
77
|
+
function: validate
|
|
78
|
+
conditions:
|
|
79
|
+
all:
|
|
80
|
+
- fact: results.config.validation.enabled
|
|
81
|
+
operator: equal
|
|
82
|
+
value: true
|
|
83
|
+
allow_new_chains: false
|
|
84
|
+
|
|
85
|
+
# Relationship 4b: Developer feedback -> Validator (when validation enabled)
|
|
86
|
+
- name: fleet-feedback-to-validate
|
|
87
|
+
trigger:
|
|
88
|
+
extension: developer
|
|
89
|
+
runner: developer
|
|
90
|
+
function: incorporate_feedback
|
|
91
|
+
action:
|
|
92
|
+
extension: validator
|
|
93
|
+
runner: validator
|
|
94
|
+
function: validate
|
|
95
|
+
conditions:
|
|
96
|
+
all:
|
|
97
|
+
- fact: results.config.validation.enabled
|
|
98
|
+
operator: equal
|
|
99
|
+
value: true
|
|
100
|
+
allow_new_chains: false
|
|
101
|
+
|
|
102
|
+
# Relationship 4c: Developer feedback -> Escalate (when results.escalate == true)
|
|
103
|
+
- name: fleet-feedback-to-escalate
|
|
104
|
+
trigger:
|
|
105
|
+
extension: developer
|
|
106
|
+
runner: developer
|
|
107
|
+
function: incorporate_feedback
|
|
108
|
+
action:
|
|
109
|
+
extension: assessor
|
|
110
|
+
runner: assessor
|
|
111
|
+
function: escalate
|
|
112
|
+
conditions:
|
|
113
|
+
all:
|
|
114
|
+
- fact: results.escalate
|
|
115
|
+
operator: equal
|
|
116
|
+
value: true
|
|
117
|
+
allow_new_chains: false
|
|
118
|
+
|
|
119
|
+
# Relationship 5: Developer -> Ship (if validation disabled)
|
|
120
|
+
- name: fleet-develop-to-ship
|
|
121
|
+
trigger:
|
|
122
|
+
extension: developer
|
|
123
|
+
runner: developer
|
|
124
|
+
function: implement
|
|
125
|
+
action:
|
|
126
|
+
extension: developer
|
|
127
|
+
runner: ship
|
|
128
|
+
function: finalize
|
|
129
|
+
conditions:
|
|
130
|
+
all:
|
|
131
|
+
- fact: results.config.validation.enabled
|
|
132
|
+
operator: equal
|
|
133
|
+
value: false
|
|
134
|
+
allow_new_chains: false
|
|
135
|
+
|
|
136
|
+
# Relationship 6: Validator -> Ship (approved)
|
|
137
|
+
- name: fleet-validate-to-ship
|
|
138
|
+
trigger:
|
|
139
|
+
extension: validator
|
|
140
|
+
runner: validator
|
|
141
|
+
function: validate
|
|
142
|
+
action:
|
|
143
|
+
extension: developer
|
|
144
|
+
runner: ship
|
|
145
|
+
function: finalize
|
|
146
|
+
conditions:
|
|
147
|
+
all:
|
|
148
|
+
- fact: results.pipeline.review_result.verdict
|
|
149
|
+
operator: equal
|
|
150
|
+
value: approved
|
|
151
|
+
allow_new_chains: false
|
|
152
|
+
|
|
153
|
+
# Relationship 7: Validator -> Developer feedback (rejected, under limit)
|
|
154
|
+
# NOTE: value 4 (not 5) because attempt starts at 0 and increments before
|
|
155
|
+
# re-entering implement. With value=4: attempts 0,1,2,3 retry (4 retries).
|
|
156
|
+
# Attempt 4 escalates. This gives exactly max_iterations=5 total runs.
|
|
157
|
+
# IMPORTANT: This hardcoded value is a safety net. The developer's
|
|
158
|
+
# incorporate_feedback runner checks the per-item limit internally,
|
|
159
|
+
# allowing different max_iterations per work item without reseeding.
|
|
160
|
+
- name: fleet-validate-to-feedback
|
|
161
|
+
trigger:
|
|
162
|
+
extension: validator
|
|
163
|
+
runner: validator
|
|
164
|
+
function: validate
|
|
165
|
+
action:
|
|
166
|
+
extension: developer
|
|
167
|
+
runner: developer
|
|
168
|
+
function: incorporate_feedback
|
|
169
|
+
conditions:
|
|
170
|
+
all:
|
|
171
|
+
- fact: results.pipeline.review_result.verdict
|
|
172
|
+
operator: equal
|
|
173
|
+
value: rejected
|
|
174
|
+
- fact: results.pipeline.attempt
|
|
175
|
+
operator: less_than
|
|
176
|
+
value: 4
|
|
177
|
+
allow_new_chains: false
|
|
178
|
+
|
|
179
|
+
# Relationship 8: Validator -> Escalate (rejected, at limit)
|
|
180
|
+
# NOTE: Safety-net fallback. Primary enforcement is in incorporate_feedback.
|
|
181
|
+
- name: fleet-validate-to-escalate
|
|
182
|
+
trigger:
|
|
183
|
+
extension: validator
|
|
184
|
+
runner: validator
|
|
185
|
+
function: validate
|
|
186
|
+
action:
|
|
187
|
+
extension: assessor
|
|
188
|
+
runner: assessor
|
|
189
|
+
function: escalate
|
|
190
|
+
conditions:
|
|
191
|
+
all:
|
|
192
|
+
- fact: results.pipeline.review_result.verdict
|
|
193
|
+
operator: equal
|
|
194
|
+
value: rejected
|
|
195
|
+
- fact: results.pipeline.attempt
|
|
196
|
+
operator: greater_or_equal
|
|
197
|
+
value: 4
|
|
198
|
+
allow_new_chains: false
|
|
199
|
+
|
|
200
|
+
settings:
|
|
201
|
+
fleet:
|
|
202
|
+
enabled: true
|
|
203
|
+
sources: []
|
|
204
|
+
llm:
|
|
205
|
+
routing:
|
|
206
|
+
escalation:
|
|
207
|
+
enabled: true
|
|
208
|
+
planning:
|
|
209
|
+
enabled: true
|
|
210
|
+
solvers: 1
|
|
211
|
+
validators: 1
|
|
212
|
+
max_iterations: 2
|
|
213
|
+
implementation:
|
|
214
|
+
solvers: 1
|
|
215
|
+
validators: 3
|
|
216
|
+
max_iterations: 5
|
|
217
|
+
validation:
|
|
218
|
+
enabled: true
|
|
219
|
+
run_tests: true
|
|
220
|
+
run_lint: true
|
|
221
|
+
security_scan: true
|
|
222
|
+
adversarial_review: true
|
|
223
|
+
feedback:
|
|
224
|
+
drain_enabled: true
|
|
225
|
+
max_drain_rounds: 3
|
|
226
|
+
summarize_after: 2
|
|
227
|
+
workspace:
|
|
228
|
+
isolation: worktree
|
|
229
|
+
cleanup_on_complete: true
|
|
230
|
+
context:
|
|
231
|
+
load_repo_docs: true
|
|
232
|
+
load_file_tree: true
|
|
233
|
+
max_context_files: 50
|
|
234
|
+
tracing:
|
|
235
|
+
stage_comments: true
|
|
236
|
+
token_tracking: true
|
|
237
|
+
safety:
|
|
238
|
+
poison_message_threshold: 2
|
|
239
|
+
cancel_allowed: true
|
|
240
|
+
selection:
|
|
241
|
+
strategy: test_winner
|
|
242
|
+
escalation:
|
|
243
|
+
on_max_iterations: human
|
|
244
|
+
consent_domain: fleet.shipping
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module Fleet
|
|
8
|
+
module SettingsDefaults
|
|
9
|
+
DEFAULTS = {
|
|
10
|
+
fleet: {
|
|
11
|
+
enabled: true,
|
|
12
|
+
sources: [],
|
|
13
|
+
llm: {
|
|
14
|
+
routing: {
|
|
15
|
+
escalation: {
|
|
16
|
+
enabled: true
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
planning: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
solvers: 1,
|
|
23
|
+
validators: 1,
|
|
24
|
+
max_iterations: 2
|
|
25
|
+
},
|
|
26
|
+
implementation: {
|
|
27
|
+
solvers: 1,
|
|
28
|
+
validators: 3,
|
|
29
|
+
max_iterations: 5
|
|
30
|
+
},
|
|
31
|
+
validation: {
|
|
32
|
+
enabled: true,
|
|
33
|
+
run_tests: true,
|
|
34
|
+
run_lint: true,
|
|
35
|
+
security_scan: true,
|
|
36
|
+
adversarial_review: true
|
|
37
|
+
},
|
|
38
|
+
feedback: {
|
|
39
|
+
drain_enabled: true,
|
|
40
|
+
max_drain_rounds: 3,
|
|
41
|
+
summarize_after: 2
|
|
42
|
+
},
|
|
43
|
+
workspace: {
|
|
44
|
+
isolation: :worktree,
|
|
45
|
+
cleanup_on_complete: true
|
|
46
|
+
},
|
|
47
|
+
context: {
|
|
48
|
+
load_repo_docs: true,
|
|
49
|
+
load_file_tree: true,
|
|
50
|
+
max_context_files: 50
|
|
51
|
+
},
|
|
52
|
+
tracing: {
|
|
53
|
+
stage_comments: true,
|
|
54
|
+
token_tracking: true
|
|
55
|
+
},
|
|
56
|
+
safety: {
|
|
57
|
+
poison_message_threshold: 2,
|
|
58
|
+
cancel_allowed: true
|
|
59
|
+
},
|
|
60
|
+
selection: {
|
|
61
|
+
strategy: :test_winner
|
|
62
|
+
},
|
|
63
|
+
escalation: {
|
|
64
|
+
on_max_iterations: :human,
|
|
65
|
+
consent_domain: 'fleet.shipping'
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}.freeze
|
|
69
|
+
|
|
70
|
+
def self.defaults
|
|
71
|
+
DEFAULTS
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.write_settings_file(path, force: false)
|
|
75
|
+
return { success: false, reason: :exists } if File.exist?(path) && !force
|
|
76
|
+
|
|
77
|
+
::FileUtils.mkdir_p(File.dirname(path))
|
|
78
|
+
File.write(path, ::JSON.pretty_generate(DEFAULTS))
|
|
79
|
+
{ success: true, path: path }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/legion/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: legionio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.8.
|
|
4
|
+
version: 1.8.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -475,6 +475,7 @@ files:
|
|
|
475
475
|
- lib/legion/api/default_settings.rb
|
|
476
476
|
- lib/legion/api/events.rb
|
|
477
477
|
- lib/legion/api/extensions.rb
|
|
478
|
+
- lib/legion/api/fleet.rb
|
|
478
479
|
- lib/legion/api/gaia.rb
|
|
479
480
|
- lib/legion/api/governance.rb
|
|
480
481
|
- lib/legion/api/graphql.rb
|
|
@@ -665,6 +666,8 @@ files:
|
|
|
665
666
|
- lib/legion/cli/eval_command.rb
|
|
666
667
|
- lib/legion/cli/failover_command.rb
|
|
667
668
|
- lib/legion/cli/features_command.rb
|
|
669
|
+
- lib/legion/cli/fleet_command.rb
|
|
670
|
+
- lib/legion/cli/fleet_setup.rb
|
|
668
671
|
- lib/legion/cli/function.rb
|
|
669
672
|
- lib/legion/cli/gaia_command.rb
|
|
670
673
|
- lib/legion/cli/generate_command.rb
|
|
@@ -845,7 +848,10 @@ files:
|
|
|
845
848
|
- lib/legion/extensions/hooks/base.rb
|
|
846
849
|
- lib/legion/extensions/permissions.rb
|
|
847
850
|
- lib/legion/extensions/transport.rb
|
|
851
|
+
- lib/legion/fleet/conditioner_rules.rb
|
|
852
|
+
- lib/legion/fleet/manifest.yml
|
|
848
853
|
- lib/legion/fleet/settings.rb
|
|
854
|
+
- lib/legion/fleet/settings_defaults.rb
|
|
849
855
|
- lib/legion/graph/builder.rb
|
|
850
856
|
- lib/legion/graph/exporter.rb
|
|
851
857
|
- lib/legion/guardrails.rb
|