legionio 1.6.3 → 1.6.7
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 +34 -0
- data/lib/legion/api/codegen.rb +84 -0
- data/lib/legion/api.rb +2 -0
- data/lib/legion/cli/codegen_command.rb +104 -0
- data/lib/legion/cli.rb +4 -0
- data/lib/legion/extensions/actors/subscription.rb +3 -2
- data/lib/legion/service.rb +10 -0
- data/lib/legion/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e025887526f8b9e258eb168a14a83d5c34e60668eadbe9e1ccd5c995e27d725c
|
|
4
|
+
data.tar.gz: a85e52dc5ec39293461e7df402f2ea8f1a410d293c28141fc935cfb6023b71df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ecefca3ab028b370f8e590cf4fa81290e3a9e106f53a5655d11928f40ec99a0d48451ab94c9516086fd81bd5c42d4523eae4d975b9df667d8db7010656884c6
|
|
7
|
+
data.tar.gz: c5f4aa31b5431e6fb012991fe0048ac75c0d25d95e6402983590d7954491ad53e78b18d2911cb7969427cd60e63a47975d7ca89ff7642857cd857328dec559e3
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.7] - 2026-03-26
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- `setup_generated_functions` now runs only when `extensions: true` (inside the extensions gate) preventing unexpected boot side-effects in CLI flows that disable extensions
|
|
7
|
+
- Consumer tag entropy upgraded from `SecureRandom.hex(4)` (32-bit) to `SecureRandom.uuid` (122-bit) in both `prepare` and `subscribe` paths of subscription actor, eliminating the theoretical RabbitMQ `NOT_ALLOWED` tag collision
|
|
8
|
+
|
|
9
|
+
## [1.6.6] - 2026-03-26
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `legionio bootstrap SOURCE` command: combines `config import`, `config scaffold`, and `setup agentic` into one command
|
|
13
|
+
- Pre-flight checks for klist (Kerberos ticket), brew availability, and legionio binary
|
|
14
|
+
- `--skip-packs` flag to skip gem pack installation (config-only mode)
|
|
15
|
+
- `--start` flag to start redis + legionio via brew services after bootstrap
|
|
16
|
+
- `--force` flag to overwrite existing config files during bootstrap
|
|
17
|
+
- `--json` flag for machine-readable bootstrap output
|
|
18
|
+
- `shell_capture` helper extracted to make shell invocations stubbable in specs
|
|
19
|
+
- 62 specs covering preflight checks, pack extraction, config fetch/write delegation, pack install, summary output, all flags, and error handling
|
|
20
|
+
|
|
21
|
+
## [1.6.5] - 2026-03-26
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- `Context.to_system_prompt` appends a live self-awareness section from `lex-agentic-self` Metacognition when the gem is loaded; logic extracted into `self_awareness_hint` helper to keep `to_system_prompt` within Metrics/CyclomaticComplexity limits; guarded with `defined?()` and `rescue StandardError`
|
|
25
|
+
|
|
26
|
+
## [1.6.4] - 2026-03-26
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- fix consumer tag collision on boot: subscription actors using `Thread.current.object_id` produced duplicate tags when `FixedThreadPool` reused threads, causing RabbitMQ `NOT_ALLOWED` connection kill and cascading errors; replaced with `SecureRandom.hex(4)`
|
|
30
|
+
|
|
3
31
|
## [1.6.3] - 2026-03-26
|
|
4
32
|
|
|
5
33
|
### Changed
|
|
@@ -24,6 +52,12 @@
|
|
|
24
52
|
## [1.6.0] - 2026-03-26
|
|
25
53
|
|
|
26
54
|
### Added
|
|
55
|
+
- `legion codegen` CLI subcommand (status, list, show, approve, reject, retry, gaps, cycle)
|
|
56
|
+
- `/api/codegen/*` API routes for generated function management
|
|
57
|
+
- Boot loading for generated functions via GeneratedRegistry
|
|
58
|
+
- Function metadata DSL (function_outputs, function_category, function_tags, function_risk_tier, function_idempotent, function_requires, function_expose)
|
|
59
|
+
- ClassMethods for MCP tool exposure (expose_as_mcp_tool, mcp_tool_prefix)
|
|
60
|
+
- End-to-end integration test for self-generating functions
|
|
27
61
|
- `legion knowledge monitor add/list/remove/status` — multi-directory corpus monitor management
|
|
28
62
|
- `legion knowledge capture commit` — capture git commit as knowledge (hook-compatible)
|
|
29
63
|
- `legion knowledge capture session` — capture session summary as knowledge (hook-compatible)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
class API < Sinatra::Base
|
|
5
|
+
module Routes
|
|
6
|
+
module Codegen
|
|
7
|
+
def self.registered(app) # rubocop:disable Metrics/MethodLength
|
|
8
|
+
app.get '/api/codegen/status' do
|
|
9
|
+
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503) unless defined?(Legion::MCP::SelfGenerate)
|
|
10
|
+
|
|
11
|
+
json_response(Legion::MCP::SelfGenerate.status)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
app.get '/api/codegen/generated' do
|
|
15
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
16
|
+
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
status_filter = params[:status]
|
|
20
|
+
records = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.list(status: status_filter)
|
|
21
|
+
json_response(records)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
app.get '/api/codegen/generated/:id' do |id|
|
|
25
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
26
|
+
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
record = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.get(id: id)
|
|
30
|
+
halt 404, json_error('not_found', 'record not found', status_code: 404) unless record
|
|
31
|
+
|
|
32
|
+
json_response(record)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
app.post '/api/codegen/generated/:id/approve' do |id|
|
|
36
|
+
unless defined?(Legion::Extensions::Codegen::Runners::ReviewHandler)
|
|
37
|
+
halt 503, json_error('codegen_unavailable', 'review handler not available', status_code: 503)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
result = Legion::Extensions::Codegen::Runners::ReviewHandler.handle_verdict(
|
|
41
|
+
review: { generation_id: id, verdict: :approve, confidence: 1.0 }
|
|
42
|
+
)
|
|
43
|
+
json_response(result)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
app.post '/api/codegen/generated/:id/reject' do |id|
|
|
47
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
48
|
+
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'rejected')
|
|
52
|
+
json_response({ id: id, status: 'rejected' })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
app.post '/api/codegen/generated/:id/retry' do |id|
|
|
56
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
57
|
+
halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'pending')
|
|
61
|
+
json_response({ id: id, status: 'pending' })
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
app.get '/api/codegen/gaps' do
|
|
65
|
+
data = if defined?(Legion::MCP::GapDetector)
|
|
66
|
+
Legion::MCP::GapDetector.detect_gaps
|
|
67
|
+
else
|
|
68
|
+
[]
|
|
69
|
+
end
|
|
70
|
+
json_response(data)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
app.post '/api/codegen/cycle' do
|
|
74
|
+
return json_response({ triggered: false, reason: 'self_generate not available' }) unless defined?(Legion::MCP::SelfGenerate)
|
|
75
|
+
|
|
76
|
+
Legion::MCP::SelfGenerate.instance_variable_set(:@last_cycle_at, nil)
|
|
77
|
+
result = Legion::MCP::SelfGenerate.run_cycle
|
|
78
|
+
json_response(result)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/legion/api.rb
CHANGED
|
@@ -46,6 +46,7 @@ require_relative 'api/apollo'
|
|
|
46
46
|
require_relative 'api/costs'
|
|
47
47
|
require_relative 'api/traces'
|
|
48
48
|
require_relative 'api/stats'
|
|
49
|
+
require_relative 'api/codegen'
|
|
49
50
|
require_relative 'api/graphql' if defined?(GraphQL)
|
|
50
51
|
|
|
51
52
|
module Legion
|
|
@@ -137,6 +138,7 @@ module Legion
|
|
|
137
138
|
register Routes::Costs
|
|
138
139
|
register Routes::Traces
|
|
139
140
|
register Routes::Stats
|
|
141
|
+
register Routes::Codegen
|
|
140
142
|
register Routes::GraphQL if defined?(Routes::GraphQL)
|
|
141
143
|
|
|
142
144
|
use Legion::API::Middleware::RequestLogger
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module CLI
|
|
5
|
+
class CodegenCommand < Thor
|
|
6
|
+
namespace :codegen
|
|
7
|
+
|
|
8
|
+
desc 'status', 'Show codegen cycle stats, pending gaps, registry counts'
|
|
9
|
+
def status
|
|
10
|
+
if defined?(Legion::MCP::SelfGenerate)
|
|
11
|
+
data = Legion::MCP::SelfGenerate.status
|
|
12
|
+
say Legion::JSON.dump({ data: data })
|
|
13
|
+
else
|
|
14
|
+
say Legion::JSON.dump({ error: 'codegen not available' })
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc 'list', 'List generated functions'
|
|
19
|
+
method_option :status, type: :string, desc: 'Filter by status'
|
|
20
|
+
def list
|
|
21
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
22
|
+
say Legion::JSON.dump({ error: 'codegen registry not available' })
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
records = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.list(status: options[:status])
|
|
27
|
+
say Legion::JSON.dump({ data: records })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc 'show ID', 'Show details of a generated function'
|
|
31
|
+
def show(id)
|
|
32
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
33
|
+
say Legion::JSON.dump({ error: 'codegen registry not available' })
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
record = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.get(id: id)
|
|
38
|
+
if record
|
|
39
|
+
say Legion::JSON.dump({ data: record })
|
|
40
|
+
else
|
|
41
|
+
say Legion::JSON.dump({ error: 'not found' })
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc 'approve ID', 'Manually approve a parked generated function'
|
|
46
|
+
def approve(id)
|
|
47
|
+
unless defined?(Legion::Extensions::Codegen::Runners::ReviewHandler)
|
|
48
|
+
say Legion::JSON.dump({ error: 'review handler not available' })
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
result = Legion::Extensions::Codegen::Runners::ReviewHandler.handle_verdict(
|
|
53
|
+
review: { generation_id: id, verdict: :approve, confidence: 1.0 }
|
|
54
|
+
)
|
|
55
|
+
say Legion::JSON.dump({ data: result })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
desc 'reject ID', 'Manually reject a generated function'
|
|
59
|
+
def reject(id)
|
|
60
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
61
|
+
say Legion::JSON.dump({ error: 'codegen registry not available' })
|
|
62
|
+
return
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'rejected')
|
|
66
|
+
say Legion::JSON.dump({ data: { id: id, status: 'rejected' } })
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
desc 'retry ID', 'Re-queue a generated function for regeneration'
|
|
70
|
+
def retry_generation(id)
|
|
71
|
+
unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
72
|
+
say Legion::JSON.dump({ error: 'codegen registry not available' })
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'pending')
|
|
77
|
+
say Legion::JSON.dump({ data: { id: id, status: 'pending' } })
|
|
78
|
+
end
|
|
79
|
+
map 'retry' => :retry_generation
|
|
80
|
+
|
|
81
|
+
desc 'gaps', 'List detected capability gaps with priorities'
|
|
82
|
+
def gaps
|
|
83
|
+
if defined?(Legion::MCP::GapDetector)
|
|
84
|
+
detected = Legion::MCP::GapDetector.detect_gaps
|
|
85
|
+
say Legion::JSON.dump({ data: detected })
|
|
86
|
+
else
|
|
87
|
+
say Legion::JSON.dump({ error: 'gap detector not available' })
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
desc 'cycle', 'Manually trigger a generation cycle (bypass cooldown)'
|
|
92
|
+
def cycle
|
|
93
|
+
unless defined?(Legion::MCP::SelfGenerate)
|
|
94
|
+
say Legion::JSON.dump({ error: 'self_generate not available' })
|
|
95
|
+
return
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
Legion::MCP::SelfGenerate.instance_variable_set(:@last_cycle_at, nil)
|
|
99
|
+
result = Legion::MCP::SelfGenerate.run_cycle
|
|
100
|
+
say Legion::JSON.dump({ data: result })
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
data/lib/legion/cli.rb
CHANGED
|
@@ -64,6 +64,7 @@ module Legion
|
|
|
64
64
|
autoload :TraceCommand, 'legion/cli/trace_command'
|
|
65
65
|
autoload :Features, 'legion/cli/features_command'
|
|
66
66
|
autoload :Debug, 'legion/cli/debug_command'
|
|
67
|
+
autoload :CodegenCommand, 'legion/cli/codegen_command'
|
|
67
68
|
|
|
68
69
|
module Groups
|
|
69
70
|
autoload :Ai, 'legion/cli/groups/ai_group'
|
|
@@ -242,6 +243,9 @@ module Legion
|
|
|
242
243
|
subcommand 'init', Legion::CLI::Init
|
|
243
244
|
|
|
244
245
|
# --- Interactive & shortcuts ---
|
|
246
|
+
desc 'codegen SUBCOMMAND', 'Manage self-generating functions'
|
|
247
|
+
subcommand 'codegen', CodegenCommand
|
|
248
|
+
|
|
245
249
|
desc 'tty', 'Rich terminal UI (onboarding, AI chat, dashboard)'
|
|
246
250
|
subcommand 'tty', Legion::CLI::Tty
|
|
247
251
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'base'
|
|
4
4
|
require 'date'
|
|
5
|
+
require 'securerandom'
|
|
5
6
|
|
|
6
7
|
module Legion
|
|
7
8
|
module Extensions
|
|
@@ -50,7 +51,7 @@ module Legion
|
|
|
50
51
|
def prepare # rubocop:disable Metrics/AbcSize
|
|
51
52
|
@queue = queue.new
|
|
52
53
|
@queue.channel.prefetch(prefetch) if defined? prefetch
|
|
53
|
-
consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{
|
|
54
|
+
consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
|
|
54
55
|
@consumer = Bunny::Consumer.new(@queue.channel, @queue, consumer_tag, false, false)
|
|
55
56
|
@consumer.on_delivery do |delivery_info, metadata, payload|
|
|
56
57
|
message = process_message(payload, metadata, delivery_info)
|
|
@@ -150,7 +151,7 @@ module Legion
|
|
|
150
151
|
def subscribe # rubocop:disable Metrics/AbcSize
|
|
151
152
|
log.info "[Subscription] subscribing: #{lex_name}/#{runner_name}"
|
|
152
153
|
sleep(delay_start) if delay_start.positive?
|
|
153
|
-
consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{
|
|
154
|
+
consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
|
|
154
155
|
on_cancellation = block { cancel }
|
|
155
156
|
|
|
156
157
|
@consumer = @queue.subscribe(manual_ack: manual_ack, block: false, consumer_tag: consumer_tag, on_cancellation: on_cancellation) do |*rmq_message|
|
data/lib/legion/service.rb
CHANGED
|
@@ -126,6 +126,7 @@ module Legion
|
|
|
126
126
|
if extensions
|
|
127
127
|
load_extensions
|
|
128
128
|
Legion::Readiness.mark_ready(:extensions)
|
|
129
|
+
setup_generated_functions
|
|
129
130
|
end
|
|
130
131
|
|
|
131
132
|
Legion::Gaia.registry&.rediscover if gaia && defined?(Legion::Gaia) && Legion::Gaia.started?
|
|
@@ -612,6 +613,15 @@ module Legion
|
|
|
612
613
|
Legion::Extensions.hook_extensions
|
|
613
614
|
end
|
|
614
615
|
|
|
616
|
+
def setup_generated_functions
|
|
617
|
+
return unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
|
|
618
|
+
|
|
619
|
+
loaded = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.load_on_boot
|
|
620
|
+
Legion::Logging.info("Loaded #{loaded} generated functions") if defined?(Legion::Logging) && loaded.to_i.positive?
|
|
621
|
+
rescue StandardError => e
|
|
622
|
+
Legion::Logging.warn("setup_generated_functions failed: #{e.message}") if defined?(Legion::Logging)
|
|
623
|
+
end
|
|
624
|
+
|
|
615
625
|
def setup_mtls_rotation
|
|
616
626
|
enabled = Legion::Settings[:security]&.dig(:mtls, :enabled)
|
|
617
627
|
return unless enabled
|
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.6.
|
|
4
|
+
version: 1.6.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -447,6 +447,7 @@ files:
|
|
|
447
447
|
- lib/legion/api/capacity.rb
|
|
448
448
|
- lib/legion/api/catalog.rb
|
|
449
449
|
- lib/legion/api/chains.rb
|
|
450
|
+
- lib/legion/api/codegen.rb
|
|
450
451
|
- lib/legion/api/coldstart.rb
|
|
451
452
|
- lib/legion/api/costs.rb
|
|
452
453
|
- lib/legion/api/events.rb
|
|
@@ -576,6 +577,7 @@ files:
|
|
|
576
577
|
- lib/legion/cli/chat_command.rb
|
|
577
578
|
- lib/legion/cli/check/privacy_check.rb
|
|
578
579
|
- lib/legion/cli/check_command.rb
|
|
580
|
+
- lib/legion/cli/codegen_command.rb
|
|
579
581
|
- lib/legion/cli/cohort.rb
|
|
580
582
|
- lib/legion/cli/coldstart_command.rb
|
|
581
583
|
- lib/legion/cli/commit_command.rb
|