legionio 1.4.192 → 1.4.197
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/.github/workflows/publish-homebrew.yml +31 -0
- data/CHANGELOG.md +33 -0
- data/lib/legion/api/apollo.rb +2 -1
- data/lib/legion/api/costs.rb +2 -1
- data/lib/legion/cli/chat/session_store.rb +4 -2
- data/lib/legion/cli/chat/tools/generate_insights.rb +4 -2
- data/lib/legion/cli/chat/tools/model_comparison.rb +2 -1
- data/lib/legion/cli/chat/tools/save_memory.rb +2 -1
- data/lib/legion/cli/chat/tools/system_status.rb +4 -2
- data/lib/legion/cli/do_command.rb +160 -0
- data/lib/legion/cli/mind_growth_command.rb +223 -0
- data/lib/legion/cli/start.rb +6 -0
- data/lib/legion/cli.rb +22 -1
- data/lib/legion/process_role.rb +2 -1
- data/lib/legion/service.rb +13 -0
- data/lib/legion/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 547bcf88727783f2d13346201194840fac26054686d646f4c357bbd366302187
|
|
4
|
+
data.tar.gz: 90801baaf55c27542bee67621c343ef5aacb0801f245ba78291e19dc6c867c92
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 19a98825568321c1dc9fd1c78aa6d1b621aab3eb8d0e17e5fe7073192508a0e8f892276f82e7d7c45a7c952f07f645a8bfa670f9d4f3d3b0f0760fee2955c0fe
|
|
7
|
+
data.tar.gz: 3a9dd4466bf92e96b860f6b38e0976e1c5a48526b4bded1150847449e96745207baa452de506fb44dedda93eaac1f6bee9120ad3aa015a8a99bbc0db70cb9af1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to Homebrew
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
trigger-homebrew:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
if: startsWith(github.event.release.tag_name, 'v')
|
|
11
|
+
steps:
|
|
12
|
+
- name: Extract version from tag
|
|
13
|
+
id: version
|
|
14
|
+
run: |
|
|
15
|
+
TAG="${RELEASE_TAG}"
|
|
16
|
+
VERSION="${TAG#v}"
|
|
17
|
+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
18
|
+
echo "Extracted version: $VERSION"
|
|
19
|
+
env:
|
|
20
|
+
RELEASE_TAG: ${{ github.event.release.tag_name }}
|
|
21
|
+
|
|
22
|
+
- name: Trigger build-legion on homebrew-tap
|
|
23
|
+
run: |
|
|
24
|
+
gh api repos/LegionIO/homebrew-tap/dispatches \
|
|
25
|
+
-f event_type=build-legion \
|
|
26
|
+
-f "client_payload[legionio_version]=${LEGIONIO_VERSION}" \
|
|
27
|
+
-f "client_payload[ruby_version]=3.4.8" \
|
|
28
|
+
-f "client_payload[package_revision]=1"
|
|
29
|
+
env:
|
|
30
|
+
GH_TOKEN: ${{ secrets.HOMEBREW_DISPATCH_TOKEN }}
|
|
31
|
+
LEGIONIO_VERSION: ${{ steps.version.outputs.version }}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.197] - 2026-03-24
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Add debug logging to 8 swallowed `rescue StandardError` blocks in chat tools and session store: ModelComparison, SystemStatus (fetch_health, fetch_ready), SessionStore (generate_summary, read_session_meta), SaveMemory (ingest_to_apollo), GenerateInsights (scheduling_status, llm_status)
|
|
7
|
+
|
|
8
|
+
## [1.4.196] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- LLM fallback in `legion do` command: when keyword matching (`find_by_intent`) returns no results, classifies intent via `Legion::LLM.ask` against the full Capability Registry catalog
|
|
12
|
+
- Graceful degradation: LLM path only activates when both `Legion::LLM` and `Catalog::Registry` are loaded; errors fall through silently
|
|
13
|
+
|
|
14
|
+
## [1.4.195] - 2026-03-24
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- `legion do "TEXT"` CLI command: natural language intent router that matches free-text to Capability Registry entries and dispatches via daemon API or in-process Ingress
|
|
18
|
+
- `DoCommand` module with two resolution paths: daemon HTTP dispatch (like `dream`) and in-process `Registry.find_by_intent` + `Ingress.run` fallback
|
|
19
|
+
|
|
20
|
+
## [1.4.194] - 2026-03-24
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- `--lite` flag on `legion start` command: sets `LEGION_MODE=lite` and `LEGION_LOCAL=true` env vars, assigns `:lite` process role
|
|
24
|
+
- `:lite` process role in `ProcessRole::ROLES`: all subsystems enabled except `crypt: false` (Vault not needed in lite mode)
|
|
25
|
+
- `Service#lite_mode?` checks `LEGION_MODE` env var and `settings[:mode]`
|
|
26
|
+
- `setup_local_mode` handles lite mode: sets dev flag, loads Transport::Local, loads mock_vault if Crypt is defined
|
|
27
|
+
|
|
28
|
+
## [1.4.193] - 2026-03-24
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- `legion mind-growth` CLI subcommand with 10 commands: status, propose, approve, reject, build, proposals, profile, health, report, history
|
|
32
|
+
- Delegates to `Legion::Extensions::MindGrowth::Client` (lex-mind-growth extension)
|
|
33
|
+
- Guards with `require_mind_growth!` — raises `CLI::Error` when extension is not loaded
|
|
34
|
+
- Supports `--json` and `--no-color` class options on all subcommands
|
|
35
|
+
|
|
3
36
|
## [1.4.192] - 2026-03-24
|
|
4
37
|
|
|
5
38
|
### Fixed
|
data/lib/legion/api/apollo.rb
CHANGED
|
@@ -123,7 +123,8 @@ module Legion
|
|
|
123
123
|
|
|
124
124
|
def apollo_data_connected?
|
|
125
125
|
defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && !Legion::Data.connection.nil?
|
|
126
|
-
rescue StandardError
|
|
126
|
+
rescue StandardError => e
|
|
127
|
+
Legion::Logging.debug("Apollo#apollo_data_connected? check failed: #{e.message}") if defined?(Legion::Logging)
|
|
127
128
|
false
|
|
128
129
|
end
|
|
129
130
|
|
data/lib/legion/api/costs.rb
CHANGED
|
@@ -46,7 +46,8 @@ module Legion
|
|
|
46
46
|
module CostHelpers
|
|
47
47
|
def metering_available?
|
|
48
48
|
defined?(Legion::Data) && Legion::Data.respond_to?(:connection) && !Legion::Data.connection.nil?
|
|
49
|
-
rescue StandardError
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
Legion::Logging.debug("CostHelpers#metering_available? check failed: #{e.message}") if defined?(Legion::Logging)
|
|
50
51
|
false
|
|
51
52
|
end
|
|
52
53
|
|
|
@@ -90,7 +90,8 @@ module Legion
|
|
|
90
90
|
first_msg = user_messages.first[:content].to_s.strip
|
|
91
91
|
first_msg = "#{first_msg[0..120]}..." if first_msg.length > 120
|
|
92
92
|
first_msg
|
|
93
|
-
rescue StandardError
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
Legion::Logging.debug("SessionStore#generate_summary failed: #{e.message}") if defined?(Legion::Logging)
|
|
94
95
|
nil
|
|
95
96
|
end
|
|
96
97
|
|
|
@@ -102,7 +103,8 @@ module Legion
|
|
|
102
103
|
summary: data[:summary],
|
|
103
104
|
model: data[:model]
|
|
104
105
|
}
|
|
105
|
-
rescue StandardError
|
|
106
|
+
rescue StandardError => e
|
|
107
|
+
Legion::Logging.debug("SessionStore#read_session_meta failed: #{e.message}") if defined?(Legion::Logging)
|
|
106
108
|
{ message_count: nil, summary: nil, model: nil }
|
|
107
109
|
end
|
|
108
110
|
end
|
|
@@ -166,7 +166,8 @@ module Legion
|
|
|
166
166
|
end
|
|
167
167
|
result[:batch] = Legion::LLM::Batch.status if defined?(Legion::LLM::Batch)
|
|
168
168
|
result.empty? ? nil : result
|
|
169
|
-
rescue StandardError
|
|
169
|
+
rescue StandardError => e
|
|
170
|
+
Legion::Logging.debug("GenerateInsights#scheduling_status failed: #{e.message}") if defined?(Legion::Logging)
|
|
170
171
|
nil
|
|
171
172
|
end
|
|
172
173
|
|
|
@@ -181,7 +182,8 @@ module Legion
|
|
|
181
182
|
result[:shadow_evals] = s[:total_evaluations]
|
|
182
183
|
end
|
|
183
184
|
result.empty? ? nil : result
|
|
184
|
-
rescue StandardError
|
|
185
|
+
rescue StandardError => e
|
|
186
|
+
Legion::Logging.debug("GenerateInsights#llm_status failed: #{e.message}") if defined?(Legion::Logging)
|
|
185
187
|
nil
|
|
186
188
|
end
|
|
187
189
|
|
|
@@ -40,7 +40,8 @@ module Legion
|
|
|
40
40
|
Legion::LLM::CostTracker::DEFAULT_PRICING.transform_values do |v|
|
|
41
41
|
{ input: v[:input], output: v[:output] }
|
|
42
42
|
end
|
|
43
|
-
rescue StandardError
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
Legion::Logging.debug("ModelComparison#cost_tracker_pricing failed: #{e.message}") if defined?(Legion::Logging)
|
|
44
45
|
{}
|
|
45
46
|
end
|
|
46
47
|
|
|
@@ -54,7 +54,8 @@ module Legion
|
|
|
54
54
|
return nil if data[:error]
|
|
55
55
|
|
|
56
56
|
'Also ingested into Apollo knowledge graph.'
|
|
57
|
-
rescue StandardError
|
|
57
|
+
rescue StandardError => e
|
|
58
|
+
Legion::Logging.debug("SaveMemory#ingest_to_apollo failed: #{e.message}") if defined?(Legion::Logging)
|
|
58
59
|
nil
|
|
59
60
|
end
|
|
60
61
|
|
|
@@ -39,7 +39,8 @@ module Legion
|
|
|
39
39
|
api_get('/api/health')
|
|
40
40
|
rescue Errno::ECONNREFUSED
|
|
41
41
|
raise
|
|
42
|
-
rescue StandardError
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
Legion::Logging.debug("SystemStatus#fetch_health failed: #{e.message}") if defined?(Legion::Logging)
|
|
43
44
|
nil
|
|
44
45
|
end
|
|
45
46
|
|
|
@@ -47,7 +48,8 @@ module Legion
|
|
|
47
48
|
api_get('/api/ready')
|
|
48
49
|
rescue Errno::ECONNREFUSED
|
|
49
50
|
raise
|
|
50
|
-
rescue StandardError
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
Legion::Logging.debug("SystemStatus#fetch_ready failed: #{e.message}") if defined?(Legion::Logging)
|
|
51
53
|
nil
|
|
52
54
|
end
|
|
53
55
|
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module CLI
|
|
5
|
+
module DoCommand
|
|
6
|
+
class << self
|
|
7
|
+
def run(intent, formatter, options)
|
|
8
|
+
if intent.strip.empty?
|
|
9
|
+
formatter.error('Usage: legion do "describe what you want"')
|
|
10
|
+
raise SystemExit, 1
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
formatter.detail("Routing intent: #{intent}")
|
|
14
|
+
|
|
15
|
+
result = try_daemon(intent, options) || try_in_process(intent) || try_llm_classify(intent)
|
|
16
|
+
|
|
17
|
+
if result.nil?
|
|
18
|
+
formatter.error('No matching capability found')
|
|
19
|
+
formatter.detail('Try: legion lex list (to see available extensions)')
|
|
20
|
+
raise SystemExit, 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
display_result(result, formatter, options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def try_daemon(intent, options)
|
|
29
|
+
require 'net/http'
|
|
30
|
+
require 'json'
|
|
31
|
+
|
|
32
|
+
port = daemon_port(options)
|
|
33
|
+
uri = URI("http://localhost:#{port}/api/tasks")
|
|
34
|
+
body = ::JSON.generate({
|
|
35
|
+
runner_class: resolve_runner_class(intent) || return,
|
|
36
|
+
function: resolve_function(intent) || return,
|
|
37
|
+
payload: { intent: intent },
|
|
38
|
+
source: 'cli:do',
|
|
39
|
+
check_subtask: false,
|
|
40
|
+
generate_task: true
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
|
44
|
+
http.open_timeout = 3
|
|
45
|
+
http.read_timeout = 30
|
|
46
|
+
request = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
|
|
47
|
+
request.body = body
|
|
48
|
+
|
|
49
|
+
response = http.request(request)
|
|
50
|
+
::JSON.parse(response.body, symbolize_names: true)
|
|
51
|
+
rescue Errno::ECONNREFUSED, Net::OpenTimeout
|
|
52
|
+
nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def try_in_process(intent)
|
|
56
|
+
return nil unless defined?(Legion::Extensions::Catalog::Registry)
|
|
57
|
+
|
|
58
|
+
matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
|
|
59
|
+
return nil if matches.empty?
|
|
60
|
+
|
|
61
|
+
best = matches.first
|
|
62
|
+
runner_class = build_runner_class(best.extension, best.runner)
|
|
63
|
+
|
|
64
|
+
if defined?(Legion::Ingress)
|
|
65
|
+
Legion::Ingress.run(
|
|
66
|
+
payload: { intent: intent },
|
|
67
|
+
runner_class: runner_class,
|
|
68
|
+
function: best.function,
|
|
69
|
+
source: 'cli:do'
|
|
70
|
+
)
|
|
71
|
+
else
|
|
72
|
+
{ matched: best.name, runner_class: runner_class, function: best.function,
|
|
73
|
+
status: 'resolved', note: 'Daemon not running; cannot execute. Start with: legion start' }
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def try_llm_classify(intent)
|
|
78
|
+
return nil unless defined?(Legion::Extensions::Catalog::Registry) && defined?(Legion::LLM)
|
|
79
|
+
|
|
80
|
+
caps = Legion::Extensions::Catalog::Registry.capabilities
|
|
81
|
+
return nil if caps.empty?
|
|
82
|
+
|
|
83
|
+
catalog = caps.map { |c| "#{c.name}: #{c.description || "#{c.extension} #{c.runner}##{c.function}"}" }
|
|
84
|
+
prompt = "Given these capabilities:\n#{catalog.join("\n")}\n\n" \
|
|
85
|
+
"Which capability best matches this intent: \"#{intent}\"?\n" \
|
|
86
|
+
'Reply with ONLY the capability name (e.g., lex-consul:health_check:run). ' \
|
|
87
|
+
'If none match, reply NONE.'
|
|
88
|
+
|
|
89
|
+
response = Legion::LLM.ask(
|
|
90
|
+
message: prompt,
|
|
91
|
+
caller: { extension: 'legionio', tool: 'do_command', tier: 'cli' }
|
|
92
|
+
)
|
|
93
|
+
chosen = response.is_a?(Hash) ? response[:response].to_s.strip : response.to_s.strip
|
|
94
|
+
return nil if chosen.empty? || chosen.upcase == 'NONE'
|
|
95
|
+
|
|
96
|
+
cap = Legion::Extensions::Catalog::Registry.find(name: chosen)
|
|
97
|
+
return nil unless cap
|
|
98
|
+
|
|
99
|
+
runner_class = build_runner_class(cap.extension, cap.runner)
|
|
100
|
+
{ matched: cap.name, runner_class: runner_class, function: cap.function,
|
|
101
|
+
status: 'resolved', source: 'llm',
|
|
102
|
+
note: 'Daemon not running; cannot execute. Start with: legion start' }
|
|
103
|
+
rescue StandardError => e
|
|
104
|
+
Legion::Logging.debug("DoCommand#try_llm_classify failed: #{e.message}") if defined?(Legion::Logging)
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def resolve_runner_class(intent)
|
|
109
|
+
return nil unless defined?(Legion::Extensions::Catalog::Registry)
|
|
110
|
+
|
|
111
|
+
matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
|
|
112
|
+
return nil if matches.empty?
|
|
113
|
+
|
|
114
|
+
build_runner_class(matches.first.extension, matches.first.runner)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def resolve_function(intent)
|
|
118
|
+
return nil unless defined?(Legion::Extensions::Catalog::Registry)
|
|
119
|
+
|
|
120
|
+
matches = Legion::Extensions::Catalog::Registry.find_by_intent(intent)
|
|
121
|
+
return nil if matches.empty?
|
|
122
|
+
|
|
123
|
+
matches.first.function
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_runner_class(extension, runner)
|
|
127
|
+
ext_part = extension.delete_prefix('lex-').split(/[-_]/).map(&:capitalize).join
|
|
128
|
+
"Legion::Extensions::#{ext_part}::Runners::#{runner}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def daemon_port(options)
|
|
132
|
+
options[:http_port] || begin
|
|
133
|
+
require 'legion/settings'
|
|
134
|
+
Legion::Settings.load unless Legion::Settings.loaded?
|
|
135
|
+
Legion::Settings.dig(:api, :port) || 4567
|
|
136
|
+
rescue StandardError
|
|
137
|
+
4567
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def display_result(result, formatter, options)
|
|
142
|
+
if options[:json]
|
|
143
|
+
formatter.json(result)
|
|
144
|
+
elsif result.is_a?(Hash) && result[:error]
|
|
145
|
+
formatter.error(result.dig(:error, :message) || result[:error].to_s)
|
|
146
|
+
elsif result.is_a?(Hash) && result[:data]
|
|
147
|
+
formatter.success('Task dispatched')
|
|
148
|
+
formatter.detail(result[:data])
|
|
149
|
+
elsif result.is_a?(Hash) && result[:matched]
|
|
150
|
+
formatter.success("Matched: #{result[:matched]}")
|
|
151
|
+
formatter.detail(result.except(:matched))
|
|
152
|
+
else
|
|
153
|
+
formatter.success('Done')
|
|
154
|
+
formatter.detail(result)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'thor'
|
|
5
|
+
|
|
6
|
+
module Legion
|
|
7
|
+
module CLI
|
|
8
|
+
class MindGrowth < Thor
|
|
9
|
+
def self.exit_on_failure?
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
|
|
14
|
+
class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
|
|
15
|
+
class_option :verbose, type: :boolean, default: false, aliases: ['-V'], desc: 'Verbose logging'
|
|
16
|
+
|
|
17
|
+
desc 'status', 'Show mind-growth cycle status'
|
|
18
|
+
def status
|
|
19
|
+
require_mind_growth!
|
|
20
|
+
result = mind_growth_client.growth_status
|
|
21
|
+
out = formatter
|
|
22
|
+
if options[:json]
|
|
23
|
+
out.json(result)
|
|
24
|
+
else
|
|
25
|
+
out.header('Mind-Growth Status')
|
|
26
|
+
out.spacer
|
|
27
|
+
out.detail(result)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
desc 'propose', 'Propose a new cognitive concept'
|
|
32
|
+
option :category, type: :string, desc: 'Cognitive category'
|
|
33
|
+
option :description, type: :string, desc: 'Concept description'
|
|
34
|
+
option :name, type: :string, desc: 'Concept name'
|
|
35
|
+
def propose
|
|
36
|
+
require_mind_growth!
|
|
37
|
+
result = mind_growth_client.propose_concept(
|
|
38
|
+
category: options[:category]&.to_sym,
|
|
39
|
+
description: options[:description],
|
|
40
|
+
name: options[:name]
|
|
41
|
+
)
|
|
42
|
+
out = formatter
|
|
43
|
+
if options[:json]
|
|
44
|
+
out.json(result)
|
|
45
|
+
elsif result[:success]
|
|
46
|
+
out.success("Proposal created: #{result.dig(:proposal, :id)}")
|
|
47
|
+
else
|
|
48
|
+
out.warn("Proposal failed: #{result[:error]}")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc 'approve ID', 'Approve a proposal'
|
|
53
|
+
def approve(proposal_id)
|
|
54
|
+
require_mind_growth!
|
|
55
|
+
result = mind_growth_client.evaluate_proposal(proposal_id: proposal_id)
|
|
56
|
+
out = formatter
|
|
57
|
+
if options[:json]
|
|
58
|
+
out.json(result)
|
|
59
|
+
elsif result[:success]
|
|
60
|
+
status_label = result[:approved] ? 'approved' : 'rejected'
|
|
61
|
+
out.success("Proposal #{proposal_id[0, 8]} #{status_label}")
|
|
62
|
+
else
|
|
63
|
+
out.warn("Evaluation failed: #{result[:error]}")
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
desc 'reject ID', 'Reject a proposal'
|
|
68
|
+
map 'reject' => :reject_proposal
|
|
69
|
+
option :reason, type: :string, desc: 'Rejection reason'
|
|
70
|
+
def reject_proposal(proposal_id)
|
|
71
|
+
require_mind_growth!
|
|
72
|
+
proposal = Legion::Extensions::MindGrowth::Runners::Proposer.get_proposal_object(proposal_id)
|
|
73
|
+
out = formatter
|
|
74
|
+
if proposal.nil?
|
|
75
|
+
out.warn("Proposal #{proposal_id[0, 8]} not found")
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
proposal.transition!(:rejected)
|
|
79
|
+
if options[:json]
|
|
80
|
+
out.json({ success: true, proposal_id: proposal_id, status: 'rejected',
|
|
81
|
+
reason: options[:reason] })
|
|
82
|
+
else
|
|
83
|
+
out.success("Proposal #{proposal_id[0, 8]} rejected")
|
|
84
|
+
end
|
|
85
|
+
rescue ArgumentError => e
|
|
86
|
+
formatter.warn("Cannot reject: #{e.message}")
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
desc 'build ID', 'Force-build an approved proposal'
|
|
90
|
+
def build(proposal_id)
|
|
91
|
+
require_mind_growth!
|
|
92
|
+
result = mind_growth_client.build_extension(proposal_id: proposal_id)
|
|
93
|
+
out = formatter
|
|
94
|
+
if options[:json]
|
|
95
|
+
out.json(result)
|
|
96
|
+
elsif result[:success]
|
|
97
|
+
out.success("Build pipeline started for #{proposal_id[0, 8]}")
|
|
98
|
+
out.detail(result[:pipeline]) if result[:pipeline]
|
|
99
|
+
else
|
|
100
|
+
out.warn("Build failed: #{result[:error]}")
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
desc 'proposals', 'List proposals'
|
|
105
|
+
option :status, type: :string, desc: 'Filter by status'
|
|
106
|
+
option :limit, type: :numeric, default: 20, desc: 'Max results'
|
|
107
|
+
def proposals
|
|
108
|
+
require_mind_growth!
|
|
109
|
+
result = mind_growth_client.list_proposals(
|
|
110
|
+
status: options[:status]&.to_sym,
|
|
111
|
+
limit: options[:limit]
|
|
112
|
+
)
|
|
113
|
+
out = formatter
|
|
114
|
+
if options[:json]
|
|
115
|
+
out.json(result)
|
|
116
|
+
else
|
|
117
|
+
rows = (result[:proposals] || []).map do |p|
|
|
118
|
+
[p[:id].to_s[0, 8], p[:name].to_s, p[:category].to_s,
|
|
119
|
+
p[:status].to_s, p[:created_at].to_s]
|
|
120
|
+
end
|
|
121
|
+
if rows.empty?
|
|
122
|
+
out.warn('No proposals found')
|
|
123
|
+
else
|
|
124
|
+
out.table(%w[id name category status created_at], rows)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
desc 'profile', 'Show cognitive architecture profile'
|
|
130
|
+
def profile
|
|
131
|
+
require_mind_growth!
|
|
132
|
+
result = mind_growth_client.cognitive_profile
|
|
133
|
+
out = formatter
|
|
134
|
+
if options[:json]
|
|
135
|
+
out.json(result)
|
|
136
|
+
else
|
|
137
|
+
out.header('Cognitive Architecture Profile')
|
|
138
|
+
out.spacer
|
|
139
|
+
out.detail({ total_extensions: result[:total_extensions],
|
|
140
|
+
overall_coverage: result[:overall_coverage] })
|
|
141
|
+
out.spacer
|
|
142
|
+
coverage = result[:model_coverage] || {}
|
|
143
|
+
rows = coverage.map { |model, data| [model.to_s, data[:coverage].to_s, data[:missing].to_s] }
|
|
144
|
+
out.table(%w[model coverage missing], rows) unless rows.empty?
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
desc 'health', 'Show extension health and fitness scores'
|
|
149
|
+
def health
|
|
150
|
+
require_mind_growth!
|
|
151
|
+
result = mind_growth_client.validate_fitness(extensions: [])
|
|
152
|
+
out = formatter
|
|
153
|
+
if options[:json]
|
|
154
|
+
out.json(result)
|
|
155
|
+
else
|
|
156
|
+
out.header('Extension Fitness')
|
|
157
|
+
out.spacer
|
|
158
|
+
ranked = result[:ranked] || []
|
|
159
|
+
if ranked.empty?
|
|
160
|
+
out.warn('No extensions to score')
|
|
161
|
+
else
|
|
162
|
+
rows = ranked.map { |e| [e[:name].to_s, format('%.3f', e[:fitness].to_f)] }
|
|
163
|
+
out.table(%w[extension fitness], rows)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
desc 'report', 'Generate retrospective report'
|
|
169
|
+
def report
|
|
170
|
+
require_mind_growth!
|
|
171
|
+
result = mind_growth_client.session_report
|
|
172
|
+
out = formatter
|
|
173
|
+
if options[:json]
|
|
174
|
+
out.json(result)
|
|
175
|
+
else
|
|
176
|
+
out.header('Mind-Growth Report')
|
|
177
|
+
out.spacer
|
|
178
|
+
out.detail(result)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
desc 'history', 'Show recent proposal history'
|
|
183
|
+
option :limit, type: :numeric, default: 50, desc: 'Max results'
|
|
184
|
+
def history
|
|
185
|
+
require_mind_growth!
|
|
186
|
+
result = mind_growth_client.list_proposals(limit: options[:limit])
|
|
187
|
+
out = formatter
|
|
188
|
+
if options[:json]
|
|
189
|
+
out.json(result)
|
|
190
|
+
else
|
|
191
|
+
rows = (result[:proposals] || []).map do |p|
|
|
192
|
+
[p[:id].to_s[0, 8], p[:name].to_s, p[:category].to_s,
|
|
193
|
+
p[:status].to_s, p[:created_at].to_s]
|
|
194
|
+
end
|
|
195
|
+
if rows.empty?
|
|
196
|
+
out.warn('No proposals found')
|
|
197
|
+
else
|
|
198
|
+
out.table(%w[id name category status created_at], rows)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
no_commands do
|
|
204
|
+
def formatter
|
|
205
|
+
@formatter ||= Output::Formatter.new(
|
|
206
|
+
json: options[:json],
|
|
207
|
+
color: !options[:no_color]
|
|
208
|
+
)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def require_mind_growth!
|
|
212
|
+
return if defined?(Legion::Extensions::MindGrowth::Client)
|
|
213
|
+
|
|
214
|
+
raise CLI::Error, 'lex-mind-growth extension is not loaded. Install and enable it first.'
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def mind_growth_client
|
|
218
|
+
@mind_growth_client ||= Legion::Extensions::MindGrowth::Client.new
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
data/lib/legion/cli/start.rb
CHANGED
|
@@ -5,6 +5,11 @@ module Legion
|
|
|
5
5
|
module Start
|
|
6
6
|
class << self
|
|
7
7
|
def run(options)
|
|
8
|
+
if options[:lite]
|
|
9
|
+
ENV['LEGION_MODE'] = 'lite'
|
|
10
|
+
ENV['LEGION_LOCAL'] = 'true'
|
|
11
|
+
end
|
|
12
|
+
|
|
8
13
|
log_level = options[:log_level] || 'info'
|
|
9
14
|
|
|
10
15
|
require 'legion'
|
|
@@ -16,6 +21,7 @@ module Legion
|
|
|
16
21
|
api = options.fetch(:api, true)
|
|
17
22
|
service_opts = { log_level: log_level, api: api }
|
|
18
23
|
service_opts[:http_port] = options[:http_port] if options[:http_port]
|
|
24
|
+
service_opts[:role] = :lite if options[:lite]
|
|
19
25
|
Legion.instance_variable_set(:@service, Legion::Service.new(**service_opts))
|
|
20
26
|
Legion::Logging.info("Started Legion v#{Legion::VERSION}")
|
|
21
27
|
|
data/lib/legion/cli.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Legion
|
|
|
24
24
|
autoload :Commit, 'legion/cli/commit_command'
|
|
25
25
|
autoload :Pr, 'legion/cli/pr_command'
|
|
26
26
|
autoload :Review, 'legion/cli/review_command'
|
|
27
|
-
autoload :Memory,
|
|
27
|
+
autoload :Memory, 'legion/cli/memory_command'
|
|
28
|
+
autoload :MindGrowth, 'legion/cli/mind_growth_command'
|
|
28
29
|
autoload :Plan, 'legion/cli/plan_command'
|
|
29
30
|
autoload :Swarm, 'legion/cli/swarm_command'
|
|
30
31
|
autoload :Gaia, 'legion/cli/gaia_command'
|
|
@@ -54,6 +55,7 @@ module Legion
|
|
|
54
55
|
autoload :Tty, 'legion/cli/tty_command'
|
|
55
56
|
autoload :ObserveCommand, 'legion/cli/observe_command'
|
|
56
57
|
autoload :Payroll, 'legion/cli/payroll_command'
|
|
58
|
+
autoload :DoCommand, 'legion/cli/do_command'
|
|
57
59
|
autoload :Interactive, 'legion/cli/interactive'
|
|
58
60
|
autoload :Docs, 'legion/cli/docs_command'
|
|
59
61
|
autoload :Failover, 'legion/cli/failover_command'
|
|
@@ -122,6 +124,7 @@ module Legion
|
|
|
122
124
|
option :log_level, type: :string, default: 'info', desc: 'Log level (debug, info, warn, error)'
|
|
123
125
|
option :api, type: :boolean, default: true, desc: 'Start the HTTP API server'
|
|
124
126
|
option :http_port, type: :numeric, desc: 'HTTP API port (overrides settings)'
|
|
127
|
+
option :lite, type: :boolean, default: false, desc: 'Start in lite mode (no external services)'
|
|
125
128
|
def start
|
|
126
129
|
Legion::CLI::Start.run(options)
|
|
127
130
|
end
|
|
@@ -218,6 +221,9 @@ module Legion
|
|
|
218
221
|
desc 'memory SUBCOMMAND', 'Persistent project memory across sessions'
|
|
219
222
|
subcommand 'memory', Legion::CLI::Memory
|
|
220
223
|
|
|
224
|
+
desc 'mind-growth SUBCOMMAND', 'Autonomous cognitive architecture expansion'
|
|
225
|
+
subcommand 'mind-growth', Legion::CLI::MindGrowth
|
|
226
|
+
|
|
221
227
|
desc 'plan', 'Start plan mode (read-only exploration, no writes)'
|
|
222
228
|
subcommand 'plan', Legion::CLI::Plan
|
|
223
229
|
|
|
@@ -325,6 +331,21 @@ module Legion
|
|
|
325
331
|
Legion::CLI::Chat.start(['prompt', text.join(' ')] + ARGV.select { |a| a.start_with?('--') })
|
|
326
332
|
end
|
|
327
333
|
|
|
334
|
+
desc 'do TEXT', 'Route a natural language intent to the right extension'
|
|
335
|
+
long_desc <<~DESC
|
|
336
|
+
Describe what you want in plain English. Legion routes to the best
|
|
337
|
+
matching extension and runner automatically.
|
|
338
|
+
|
|
339
|
+
Examples:
|
|
340
|
+
legion do "check consul health"
|
|
341
|
+
legion do "list running tasks"
|
|
342
|
+
legion do "review the latest PR"
|
|
343
|
+
DESC
|
|
344
|
+
def do_action(*text)
|
|
345
|
+
Legion::CLI::DoCommand.run(text.join(' '), formatter, options)
|
|
346
|
+
end
|
|
347
|
+
map 'do' => :do_action
|
|
348
|
+
|
|
328
349
|
desc 'dream', 'Trigger a dream cycle on the running daemon'
|
|
329
350
|
option :wait, type: :boolean, default: false, desc: 'Wait for dream cycle to complete'
|
|
330
351
|
def dream
|
data/lib/legion/process_role.rb
CHANGED
|
@@ -6,7 +6,8 @@ module Legion
|
|
|
6
6
|
full: { transport: true, cache: true, data: true, extensions: true, api: true, llm: true, gaia: true, crypt: true, supervision: true },
|
|
7
7
|
api: { transport: true, cache: true, data: true, extensions: false, api: true, llm: false, gaia: false, crypt: true, supervision: false },
|
|
8
8
|
worker: { transport: true, cache: true, data: true, extensions: true, api: false, llm: true, gaia: true, crypt: true, supervision: true },
|
|
9
|
-
router: { transport: true, cache: true, data: false, extensions: true, api: false, llm: false, gaia: false, crypt: true, supervision: false }
|
|
9
|
+
router: { transport: true, cache: true, data: false, extensions: true, api: false, llm: false, gaia: false, crypt: true, supervision: false },
|
|
10
|
+
lite: { transport: true, cache: true, data: true, extensions: true, api: true, llm: true, gaia: true, crypt: false, supervision: true }
|
|
10
11
|
}.freeze
|
|
11
12
|
|
|
12
13
|
def self.resolve(role_name)
|
data/lib/legion/service.rb
CHANGED
|
@@ -97,6 +97,14 @@ module Legion
|
|
|
97
97
|
end
|
|
98
98
|
|
|
99
99
|
def setup_local_mode
|
|
100
|
+
if lite_mode?
|
|
101
|
+
Legion::Logging.info 'Starting in lite mode (zero infrastructure)'
|
|
102
|
+
Legion::Settings[:dev] = true
|
|
103
|
+
require 'legion/transport/local'
|
|
104
|
+
require 'legion/crypt/mock_vault' if defined?(Legion::Crypt)
|
|
105
|
+
return
|
|
106
|
+
end
|
|
107
|
+
|
|
100
108
|
return unless local_mode?
|
|
101
109
|
|
|
102
110
|
Legion::Logging.info 'Starting in local development mode'
|
|
@@ -111,6 +119,11 @@ module Legion
|
|
|
111
119
|
Legion::Settings[:local_mode] == true
|
|
112
120
|
end
|
|
113
121
|
|
|
122
|
+
def lite_mode?
|
|
123
|
+
ENV['LEGION_MODE'] == 'lite' ||
|
|
124
|
+
Legion::Settings[:mode].to_s == 'lite'
|
|
125
|
+
end
|
|
126
|
+
|
|
114
127
|
def setup_data
|
|
115
128
|
Legion::Logging.info 'Setting up Legion::Data'
|
|
116
129
|
require 'legion/data'
|
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.4.
|
|
4
|
+
version: 1.4.197
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -361,6 +361,7 @@ files:
|
|
|
361
361
|
- ".github/workflow-templates/eval-gate.yml"
|
|
362
362
|
- ".github/workflows/ci-cd.yml"
|
|
363
363
|
- ".github/workflows/ci.yml"
|
|
364
|
+
- ".github/workflows/publish-homebrew.yml"
|
|
364
365
|
- ".gitignore"
|
|
365
366
|
- ".rubocop.yml"
|
|
366
367
|
- CHANGELOG.md
|
|
@@ -548,6 +549,7 @@ files:
|
|
|
548
549
|
- lib/legion/cli/dashboard_command.rb
|
|
549
550
|
- lib/legion/cli/dataset_command.rb
|
|
550
551
|
- lib/legion/cli/detect_command.rb
|
|
552
|
+
- lib/legion/cli/do_command.rb
|
|
551
553
|
- lib/legion/cli/docs_command.rb
|
|
552
554
|
- lib/legion/cli/doctor.rb
|
|
553
555
|
- lib/legion/cli/doctor/bundle_check.rb
|
|
@@ -628,6 +630,7 @@ files:
|
|
|
628
630
|
- lib/legion/cli/marketplace_command.rb
|
|
629
631
|
- lib/legion/cli/mcp_command.rb
|
|
630
632
|
- lib/legion/cli/memory_command.rb
|
|
633
|
+
- lib/legion/cli/mind_growth_command.rb
|
|
631
634
|
- lib/legion/cli/notebook_command.rb
|
|
632
635
|
- lib/legion/cli/observe_command.rb
|
|
633
636
|
- lib/legion/cli/openapi_command.rb
|