legionio 1.9.34 → 1.9.36
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 +16 -0
- data/Gemfile +32 -29
- data/lib/legion/cli/bootstrap_command.rb +17 -5
- data/lib/legion/cli/service_command.rb +164 -0
- data/lib/legion/cli.rb +4 -0
- data/lib/legion/extensions.rb +18 -0
- data/lib/legion/identity/broker.rb +4 -0
- data/lib/legion/identity/resolver.rb +67 -0
- data/lib/legion/service.rb +26 -7
- data/lib/legion/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1de97753e1f584403f30a43a47049183871b98e7581bee36c892aac9e758983b
|
|
4
|
+
data.tar.gz: 2042c10f023e59093f60258a65ccec746128c3efef7bb980ee33206dafef446c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e206bc92436c737aa2046580af9b705d147d8601601703980d69944afbcc10189aa479527ee6f0da128eb0a80912d54dbc635f2c32eab4aaba2f7f21e595062e
|
|
7
|
+
data.tar.gz: da6fc109223bf64dced09fd289f86a3f67592d2ad9a096f64376e0ecea074d2611671eda9fecadbfc14e528a377ee1c2a06e15cc2cbde0de7ffda24c524dab3f
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.9.36] - 2026-05-22
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Identity: preload identity provider gems and resolve process identity before LLM setup so `llm.registry` availability events include Legion identity headers.
|
|
9
|
+
- Identity: use the persisted `identity.json` value as a cached resolver fallback ahead of unverified system identity when fresh auth providers are unavailable.
|
|
10
|
+
- Bundler: load sibling Legion and LLM provider path dependencies outside the test group when those local checkouts exist, so local service boots can use the active workspace gems.
|
|
11
|
+
|
|
12
|
+
## [1.9.35] - 2026-05-22
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- CLI: `legionio service start|stop|restart|status` subcommand for direct launchd control
|
|
16
|
+
- Logging transport forwarding now publishes structured log headers/properties, including identity and Legion version headers supplied by `legion-logging`.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- CLI: `legionio bootstrap --start` now calls `launchctl kickstart` after brew services start to force immediate spawn on macOS 26+ (Tahoe defers `RunAtLoad` for mid-session bootstraps)
|
|
20
|
+
|
|
5
21
|
## [1.9.34] - 2026-05-18
|
|
6
22
|
|
|
7
23
|
### Added
|
data/Gemfile
CHANGED
|
@@ -8,36 +8,39 @@ gem 'pg'
|
|
|
8
8
|
gem 'kramdown', '>= 2.0'
|
|
9
9
|
gem 'mysql2'
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
end
|
|
32
|
-
if File.exist?(File.expand_path('../extensions-identity/lex-identity-system', __dir__))
|
|
33
|
-
gem 'lex-identity-system', path: '../extensions-identity/lex-identity-system'
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
%w[anthropic azure-foundry bedrock gemini mlx ollama openai vertex vllm].each do |provider|
|
|
37
|
-
provider_path = "../extensions-ai/lex-llm-#{provider}"
|
|
38
|
-
gem "lex-llm-#{provider}", path: provider_path if File.exist?(File.expand_path(provider_path, __dir__))
|
|
39
|
-
end
|
|
11
|
+
gem 'legion-data', path: '../legion-data' if File.exist?(File.expand_path('../legion-data', __dir__))
|
|
12
|
+
gem 'legion-logging', path: '../legion-logging' if File.exist?(File.expand_path('../legion-logging', __dir__))
|
|
13
|
+
gem 'legion-settings', path: '../legion-settings' if File.exist?(File.expand_path('../legion-settings', __dir__))
|
|
14
|
+
|
|
15
|
+
gem 'legion-apollo', path: '../legion-apollo' if File.exist?(File.expand_path('../legion-apollo', __dir__))
|
|
16
|
+
gem 'legion-gaia', path: '../legion-gaia' if File.exist?(File.expand_path('../legion-gaia', __dir__))
|
|
17
|
+
gem 'legion-llm', path: '../legion-llm' if File.exist?(File.expand_path('../legion-llm', __dir__))
|
|
18
|
+
gem 'legion-mcp', path: '../legion-mcp' if File.exist?(File.expand_path('../legion-mcp', __dir__))
|
|
19
|
+
gem 'legion-tty', path: '../legion-tty' if File.exist?(File.expand_path('../legion-tty', __dir__))
|
|
20
|
+
|
|
21
|
+
gem 'lex-apollo', path: '../extensions/lex-apollo' if File.exist?(File.expand_path('../extensions/lex-apollo', __dir__))
|
|
22
|
+
gem 'lex-llm', path: '../extensions-ai/lex-llm' if File.exist?(File.expand_path('../extensions-ai/lex-llm', __dir__))
|
|
23
|
+
# gem 'lex-llm-ledger', path: '../extensions-ai/lex-llm-ledger' if File.exist?(File.expand_path('../extensions-ai/lex-llm-ledger', __dir__))
|
|
24
|
+
|
|
25
|
+
if File.exist?(File.expand_path('../extensions-identity/lex-identity-entra', __dir__))
|
|
26
|
+
gem 'lex-identity-entra', path: '../extensions-identity/lex-identity-entra'
|
|
27
|
+
end
|
|
28
|
+
if File.exist?(File.expand_path('../extensions-identity/lex-identity-kerberos', __dir__))
|
|
29
|
+
gem 'lex-identity-kerberos', path: '../extensions-identity/lex-identity-kerberos'
|
|
30
|
+
end
|
|
40
31
|
|
|
32
|
+
gem 'lex-kerberos', path: '../extensions-identity/lex-kerberos' if File.exist?(File.expand_path('../extensions-identity/lex-kerberos', __dir__))
|
|
33
|
+
|
|
34
|
+
if File.exist?(File.expand_path('../extensions-identity/lex-identity-system', __dir__))
|
|
35
|
+
gem 'lex-identity-system', path: '../extensions-identity/lex-identity-system'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
%w[anthropic azure-foundry bedrock gemini mlx ollama openai vertex vllm].each do |provider|
|
|
39
|
+
provider_path = "../extensions-ai/lex-llm-#{provider}"
|
|
40
|
+
gem "lex-llm-#{provider}", path: provider_path if File.exist?(File.expand_path(provider_path, __dir__))
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
group :test do
|
|
41
44
|
gem 'faraday'
|
|
42
45
|
gem 'faraday-net_http'
|
|
43
46
|
gem 'graphql'
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'English'
|
|
4
4
|
require 'json'
|
|
5
5
|
require 'fileutils'
|
|
6
|
+
require 'open3'
|
|
6
7
|
require 'rbconfig'
|
|
7
8
|
require 'thor'
|
|
8
9
|
require 'legion/cli/output'
|
|
@@ -391,18 +392,29 @@ module Legion
|
|
|
391
392
|
|
|
392
393
|
def run_brew_service(service, out)
|
|
393
394
|
output, success = shell_capture("brew services start #{service}")
|
|
394
|
-
|
|
395
|
-
out.success("#{service} started") unless options[:json]
|
|
396
|
-
true
|
|
397
|
-
else
|
|
395
|
+
unless success
|
|
398
396
|
out.warn("#{service} failed to start: #{output.strip.lines.last&.strip}") unless options[:json]
|
|
399
|
-
false
|
|
397
|
+
return false
|
|
400
398
|
end
|
|
399
|
+
|
|
400
|
+
out.success("#{service} started") unless options[:json]
|
|
401
|
+
kickstart_launchd_service("homebrew.mxcl.#{service}", out)
|
|
401
402
|
rescue StandardError => e
|
|
402
403
|
out.warn("brew services start #{service} raised: #{e.message}") unless options[:json]
|
|
403
404
|
false
|
|
404
405
|
end
|
|
405
406
|
|
|
407
|
+
def kickstart_launchd_service(label, out)
|
|
408
|
+
return true unless RbConfig::CONFIG['host_os'] =~ /darwin/
|
|
409
|
+
|
|
410
|
+
uid = ::Process.uid
|
|
411
|
+
_, status = Open3.capture2e('launchctl', 'kickstart', "gui/#{uid}/#{label}")
|
|
412
|
+
return true if status.success?
|
|
413
|
+
|
|
414
|
+
out.warn("launchctl kickstart #{label} failed (service may already be running)") unless options[:json]
|
|
415
|
+
false
|
|
416
|
+
end
|
|
417
|
+
|
|
406
418
|
def poll_daemon_ready(out, port: 4567, timeout: 30)
|
|
407
419
|
require 'net/http'
|
|
408
420
|
deadline = ::Time.now + timeout
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'rbconfig'
|
|
5
|
+
require 'thor'
|
|
6
|
+
require 'legion/cli/output'
|
|
7
|
+
|
|
8
|
+
module Legion
|
|
9
|
+
module CLI
|
|
10
|
+
class ServiceCommand < Thor
|
|
11
|
+
namespace 'service'
|
|
12
|
+
|
|
13
|
+
def self.exit_on_failure?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
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
|
+
SERVICE_LABEL = 'homebrew.mxcl.legionio'
|
|
21
|
+
|
|
22
|
+
desc 'start', 'Start the Legion launchd service'
|
|
23
|
+
long_desc <<~DESC
|
|
24
|
+
Starts the Legion background service via launchd. On macOS 26+ (Tahoe),
|
|
25
|
+
uses launchctl kickstart to ensure immediate process spawn after bootstrap.
|
|
26
|
+
DESC
|
|
27
|
+
def start
|
|
28
|
+
out = Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
29
|
+
ensure_macos!(out)
|
|
30
|
+
|
|
31
|
+
plist = plist_path
|
|
32
|
+
unless File.exist?(plist)
|
|
33
|
+
out.error("Service plist not found at #{plist}")
|
|
34
|
+
out.info('Run: brew install legionio')
|
|
35
|
+
raise SystemExit, 1
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
uid = ::Process.uid
|
|
39
|
+
target = "gui/#{uid}"
|
|
40
|
+
|
|
41
|
+
if service_loaded?(target)
|
|
42
|
+
out.info('Service already loaded, kicking...')
|
|
43
|
+
else
|
|
44
|
+
_, status = Open3.capture2e('launchctl', 'bootstrap', target, plist)
|
|
45
|
+
out.warn('bootstrap failed (may already be loaded), attempting kickstart anyway') unless status.success?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
_, status = Open3.capture2e('launchctl', 'kickstart', '-k', "#{target}/#{SERVICE_LABEL}")
|
|
49
|
+
if status.success?
|
|
50
|
+
out.success('Legion service started')
|
|
51
|
+
else
|
|
52
|
+
out.error('Failed to kickstart Legion service')
|
|
53
|
+
raise SystemExit, 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
poll_ready(out)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc 'stop', 'Stop the Legion launchd service'
|
|
60
|
+
def stop
|
|
61
|
+
out = Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
62
|
+
ensure_macos!(out)
|
|
63
|
+
|
|
64
|
+
uid = ::Process.uid
|
|
65
|
+
target = "gui/#{uid}"
|
|
66
|
+
|
|
67
|
+
_, status = Open3.capture2e('launchctl', 'bootout', "#{target}/#{SERVICE_LABEL}")
|
|
68
|
+
if status.success?
|
|
69
|
+
out.success('Legion service stopped')
|
|
70
|
+
else
|
|
71
|
+
out.warn('Service was not loaded (already stopped?)')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
desc 'restart', 'Restart the Legion launchd service'
|
|
76
|
+
def restart
|
|
77
|
+
out = Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
78
|
+
ensure_macos!(out)
|
|
79
|
+
|
|
80
|
+
uid = ::Process.uid
|
|
81
|
+
target = "gui/#{uid}"
|
|
82
|
+
|
|
83
|
+
Open3.capture2e('launchctl', 'bootout', "#{target}/#{SERVICE_LABEL}")
|
|
84
|
+
sleep 1
|
|
85
|
+
|
|
86
|
+
plist = plist_path
|
|
87
|
+
Open3.capture2e('launchctl', 'bootstrap', target, plist) if File.exist?(plist)
|
|
88
|
+
|
|
89
|
+
_, status = Open3.capture2e('launchctl', 'kickstart', '-k', "#{target}/#{SERVICE_LABEL}")
|
|
90
|
+
if status.success?
|
|
91
|
+
out.success('Legion service restarted')
|
|
92
|
+
else
|
|
93
|
+
out.error('Failed to restart Legion service')
|
|
94
|
+
raise SystemExit, 1
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
poll_ready(out)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
desc 'status', 'Show Legion launchd service status'
|
|
101
|
+
def status
|
|
102
|
+
out = Output::Formatter.new(json: options[:json], color: !options[:no_color])
|
|
103
|
+
ensure_macos!(out)
|
|
104
|
+
|
|
105
|
+
uid = ::Process.uid
|
|
106
|
+
target = "gui/#{uid}"
|
|
107
|
+
output, status = Open3.capture2e('launchctl', 'print', "#{target}/#{SERVICE_LABEL}")
|
|
108
|
+
|
|
109
|
+
unless status.success?
|
|
110
|
+
out.info('Service is not loaded')
|
|
111
|
+
return
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
state = output[/state = (.+)/, 1] || 'unknown'
|
|
115
|
+
pid = output[/pid = (\d+)/, 1]
|
|
116
|
+
runs = output[/runs = (\d+)/, 1]
|
|
117
|
+
|
|
118
|
+
if options[:json]
|
|
119
|
+
puts Legion::JSON.dump({ state: state, pid: pid&.to_i, runs: runs&.to_i })
|
|
120
|
+
else
|
|
121
|
+
out.info("State: #{state}")
|
|
122
|
+
out.info("PID: #{pid}") if pid
|
|
123
|
+
out.info("Runs: #{runs}") if runs
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def ensure_macos!(out)
|
|
130
|
+
return if RbConfig::CONFIG['host_os'] =~ /darwin/
|
|
131
|
+
|
|
132
|
+
out.error('The service command is only available on macOS (uses launchd)')
|
|
133
|
+
raise SystemExit, 1
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def plist_path
|
|
137
|
+
File.expand_path("~/Library/LaunchAgents/#{SERVICE_LABEL}.plist")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def service_loaded?(target)
|
|
141
|
+
_, status = Open3.capture2e('launchctl', 'print', "#{target}/#{SERVICE_LABEL}")
|
|
142
|
+
status.success?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def poll_ready(out, port: 4567, timeout: 15)
|
|
146
|
+
require 'net/http'
|
|
147
|
+
deadline = ::Time.now + timeout
|
|
148
|
+
until ::Time.now > deadline
|
|
149
|
+
begin
|
|
150
|
+
resp = Net::HTTP.get_response(URI("http://localhost:#{port}/api/ready"))
|
|
151
|
+
if resp.is_a?(Net::HTTPSuccess)
|
|
152
|
+
out.success("Daemon ready on port #{port}")
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
rescue StandardError
|
|
156
|
+
# not ready yet
|
|
157
|
+
end
|
|
158
|
+
sleep 1
|
|
159
|
+
end
|
|
160
|
+
out.info('Service started but not yet ready (boot in progress)')
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
data/lib/legion/cli.rb
CHANGED
|
@@ -69,6 +69,7 @@ module Legion
|
|
|
69
69
|
autoload :Debug, 'legion/cli/debug_command'
|
|
70
70
|
autoload :CodegenCommand, 'legion/cli/codegen_command'
|
|
71
71
|
autoload :Bootstrap, 'legion/cli/bootstrap_command'
|
|
72
|
+
autoload :ServiceCommand, 'legion/cli/service_command'
|
|
72
73
|
autoload :Broker, 'legion/cli/broker_command'
|
|
73
74
|
autoload :AdminCommand, 'legion/cli/admin_command'
|
|
74
75
|
autoload :Workflow, 'legion/cli/workflow_command'
|
|
@@ -258,6 +259,9 @@ module Legion
|
|
|
258
259
|
desc 'setup SUBCOMMAND', 'Install feature packs and configure IDE integrations'
|
|
259
260
|
subcommand 'setup', Legion::CLI::Setup
|
|
260
261
|
|
|
262
|
+
desc 'service SUBCOMMAND', 'Manage the Legion launchd background service'
|
|
263
|
+
subcommand 'service', Legion::CLI::ServiceCommand
|
|
264
|
+
|
|
261
265
|
desc 'bootstrap SOURCE', 'One-command setup: fetch config, scaffold, and install packs'
|
|
262
266
|
subcommand 'bootstrap', Legion::CLI::Bootstrap
|
|
263
267
|
|
data/lib/legion/extensions.rb
CHANGED
|
@@ -132,6 +132,24 @@ module Legion
|
|
|
132
132
|
Legion::Logging.info "[Extensions] flushed #{count} pending registrations" if defined?(Legion::Logging)
|
|
133
133
|
end
|
|
134
134
|
|
|
135
|
+
def require_identity_extensions
|
|
136
|
+
find_extensions.select { |entry| entry[:category] == :identity }.each do |entry|
|
|
137
|
+
gem_name = entry[:gem_name]
|
|
138
|
+
ext_settings = extension_settings_for_entry(entry)
|
|
139
|
+
|
|
140
|
+
if ext_settings.is_a?(Hash) && ext_settings.key?(:enabled) && !ext_settings[:enabled]
|
|
141
|
+
Legion::Logging.info "Skipping #{gem_name} identity preload because it's disabled"
|
|
142
|
+
next
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
Catalog.register(gem_name)
|
|
146
|
+
register_extension_handle(gem_name, state: :registered,
|
|
147
|
+
latest_installed_version: latest_installed_version(gem_name))
|
|
148
|
+
ensure_namespace(entry[:const_path]) if entry[:segments].length > 1
|
|
149
|
+
gem_load(entry)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
135
153
|
def pause_actors
|
|
136
154
|
@running_instances&.each do |inst|
|
|
137
155
|
timer = inst.instance_variable_get(:@timer)
|
|
@@ -21,6 +21,10 @@ module Legion
|
|
|
21
21
|
token
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
+
def credential_for(provider_name, qualifier: nil, for_context: nil, purpose: nil, context: nil)
|
|
25
|
+
token_for(provider_name, qualifier: qualifier, for_context: for_context, purpose: purpose, context: context)
|
|
26
|
+
end
|
|
27
|
+
|
|
24
28
|
def lease_for(provider_name, qualifier: nil)
|
|
25
29
|
name = provider_name.to_sym
|
|
26
30
|
resolved = qualifier || default_qualifier_for(name)
|
|
@@ -33,6 +33,12 @@ module Legion
|
|
|
33
33
|
|
|
34
34
|
winning_provider, winning_result, provider_results = resolve_auth(auth_providers, timeout: timeout)
|
|
35
35
|
|
|
36
|
+
if winning_provider.nil?
|
|
37
|
+
log.debug('resolve!: no auth winner, trying cached identity')
|
|
38
|
+
winning_provider, winning_result, cached_results = resolve_cached_identity
|
|
39
|
+
provider_results.merge!(cached_results) if cached_results
|
|
40
|
+
end
|
|
41
|
+
|
|
36
42
|
if winning_provider.nil?
|
|
37
43
|
log.debug('resolve!: no auth winner, trying fallback providers')
|
|
38
44
|
winning_provider, winning_result, fallback_results = resolve_auth(fallback_providers, timeout: timeout)
|
|
@@ -229,6 +235,67 @@ module Legion
|
|
|
229
235
|
end
|
|
230
236
|
end
|
|
231
237
|
|
|
238
|
+
def resolve_cached_identity
|
|
239
|
+
cached = read_cached_identity
|
|
240
|
+
return [nil, nil, {}] unless cached
|
|
241
|
+
|
|
242
|
+
provider = cached_identity_provider
|
|
243
|
+
result = {
|
|
244
|
+
canonical_name: cached[:canonical_name],
|
|
245
|
+
kind: cached[:kind] || :human,
|
|
246
|
+
source: :identity_json,
|
|
247
|
+
persistent: true
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
[
|
|
251
|
+
provider,
|
|
252
|
+
result,
|
|
253
|
+
{
|
|
254
|
+
provider.provider_name => {
|
|
255
|
+
status: :resolved,
|
|
256
|
+
trust: provider.trust_level,
|
|
257
|
+
resolved_at: Time.now,
|
|
258
|
+
provider: provider,
|
|
259
|
+
result: result
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def read_cached_identity
|
|
266
|
+
path = File.expand_path('~/.legionio/settings/identity.json')
|
|
267
|
+
return nil unless File.file?(path)
|
|
268
|
+
|
|
269
|
+
data = if defined?(Legion::JSON)
|
|
270
|
+
Legion::JSON.load(File.read(path))
|
|
271
|
+
else
|
|
272
|
+
require 'json'
|
|
273
|
+
::JSON.parse(File.read(path), symbolize_names: true)
|
|
274
|
+
end
|
|
275
|
+
canonical = data[:canonical_name] || data['canonical_name']
|
|
276
|
+
return nil if canonical.to_s.strip.empty?
|
|
277
|
+
|
|
278
|
+
{
|
|
279
|
+
canonical_name: canonical.to_s,
|
|
280
|
+
kind: (data[:kind] || data['kind'] || :human).to_sym
|
|
281
|
+
}
|
|
282
|
+
rescue StandardError => e
|
|
283
|
+
log.warn("identity.json read failed: #{e.message}")
|
|
284
|
+
nil
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def cached_identity_provider
|
|
288
|
+
@cached_identity_provider ||= Module.new do
|
|
289
|
+
module_function
|
|
290
|
+
|
|
291
|
+
def provider_name = :identity_cache
|
|
292
|
+
def provider_type = :auth
|
|
293
|
+
def priority = -100
|
|
294
|
+
def trust_weight = 150
|
|
295
|
+
def trust_level = :cached
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
232
299
|
def auth_future_status(future, result)
|
|
233
300
|
if future.rejected?
|
|
234
301
|
:failed
|
data/lib/legion/service.rb
CHANGED
|
@@ -112,6 +112,8 @@ module Legion
|
|
|
112
112
|
end
|
|
113
113
|
setup_cluster if data
|
|
114
114
|
|
|
115
|
+
setup_identity_before_llm(extensions: extensions, transport: transport)
|
|
116
|
+
|
|
115
117
|
if llm
|
|
116
118
|
begin
|
|
117
119
|
setup_llm
|
|
@@ -161,15 +163,14 @@ module Legion
|
|
|
161
163
|
setup_safety_metrics
|
|
162
164
|
setup_supervision if supervision
|
|
163
165
|
|
|
164
|
-
require_relative 'identity' if File.exist?(File.expand_path('identity.rb', __dir__))
|
|
165
|
-
|
|
166
166
|
if extensions
|
|
167
167
|
load_extensions
|
|
168
168
|
Legion::Readiness.mark_ready(:extensions)
|
|
169
169
|
setup_generated_functions
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
-
#
|
|
172
|
+
# Re-run identity after full extension load so any providers with autobuild-time
|
|
173
|
+
# registration can upgrade the pre-LLM identity.
|
|
173
174
|
db_available = defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
|
|
174
175
|
setup_identity if transport || db_available
|
|
175
176
|
register_credential_providers if extensions && (transport || db_available)
|
|
@@ -589,7 +590,7 @@ module Legion
|
|
|
589
590
|
end
|
|
590
591
|
end
|
|
591
592
|
|
|
592
|
-
def setup_logging_transport # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
|
|
593
|
+
def setup_logging_transport # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
|
|
593
594
|
return unless defined?(Legion::Transport::Connection)
|
|
594
595
|
return unless Legion::Transport::Connection.session_open?
|
|
595
596
|
|
|
@@ -612,11 +613,16 @@ module Legion
|
|
|
612
613
|
exchange = log_channel.topic('legion.logging', durable: true)
|
|
613
614
|
|
|
614
615
|
if forward_logs
|
|
615
|
-
Legion::Logging.log_writer = lambda { |event, routing_key
|
|
616
|
+
Legion::Logging.log_writer = lambda { |event, routing_key:, headers: {}, properties: {}|
|
|
616
617
|
begin
|
|
617
618
|
next unless log_channel&.open?
|
|
618
619
|
|
|
619
|
-
exchange.publish(
|
|
620
|
+
exchange.publish(
|
|
621
|
+
Legion::JSON.dump(event),
|
|
622
|
+
routing_key: routing_key,
|
|
623
|
+
headers: headers,
|
|
624
|
+
**properties
|
|
625
|
+
)
|
|
620
626
|
rescue StandardError
|
|
621
627
|
nil
|
|
622
628
|
end
|
|
@@ -918,6 +924,8 @@ module Legion
|
|
|
918
924
|
Legion::Readiness.mark_skipped(:rbac)
|
|
919
925
|
end
|
|
920
926
|
|
|
927
|
+
setup_identity_before_llm(extensions: true, transport: true)
|
|
928
|
+
|
|
921
929
|
if defined?(Legion::LLM)
|
|
922
930
|
setup_llm
|
|
923
931
|
else
|
|
@@ -945,7 +953,6 @@ module Legion
|
|
|
945
953
|
# Phase 5: re-run identity resolution after extensions are loaded so that
|
|
946
954
|
# any identity providers registered by lex-identity-* extensions are
|
|
947
955
|
# available to the resolver (mirrors the boot-time ordering).
|
|
948
|
-
Legion::Identity::Resolver.reset! if defined?(Legion::Identity::Resolver)
|
|
949
956
|
setup_identity
|
|
950
957
|
|
|
951
958
|
db_available = defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
|
|
@@ -976,6 +983,18 @@ module Legion
|
|
|
976
983
|
Legion::Extensions.hook_extensions
|
|
977
984
|
end
|
|
978
985
|
|
|
986
|
+
def setup_identity_before_llm(extensions:, transport:)
|
|
987
|
+
require_relative 'identity' if File.exist?(File.expand_path('identity.rb', __dir__))
|
|
988
|
+
Legion::Extensions.require_identity_extensions if extensions &&
|
|
989
|
+
defined?(Legion::Extensions) &&
|
|
990
|
+
Legion::Extensions.respond_to?(:require_identity_extensions)
|
|
991
|
+
|
|
992
|
+
db_available = defined?(Legion::Data) && Legion::Data.respond_to?(:connected?) && Legion::Data.connected?
|
|
993
|
+
setup_identity if transport || db_available
|
|
994
|
+
rescue StandardError => e
|
|
995
|
+
handle_exception(e, level: :warn, operation: 'service.setup_identity_before_llm')
|
|
996
|
+
end
|
|
997
|
+
|
|
979
998
|
def register_core_tools
|
|
980
999
|
require 'legion/tools'
|
|
981
1000
|
Legion::Tools.register_all
|
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.9.
|
|
4
|
+
version: 1.9.36
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -750,6 +750,7 @@ files:
|
|
|
750
750
|
- lib/legion/cli/relationship.rb
|
|
751
751
|
- lib/legion/cli/review_command.rb
|
|
752
752
|
- lib/legion/cli/schedule_command.rb
|
|
753
|
+
- lib/legion/cli/service_command.rb
|
|
753
754
|
- lib/legion/cli/setup_command.rb
|
|
754
755
|
- lib/legion/cli/skill_command.rb
|
|
755
756
|
- lib/legion/cli/start.rb
|