legionio 1.4.186 → 1.4.187
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 +9 -0
- data/lib/legion/extensions/capability.rb +51 -0
- data/lib/legion/extensions/catalog/registry.rb +109 -0
- data/lib/legion/extensions/catalog.rb +2 -0
- data/lib/legion/extensions.rb +37 -1
- 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: b09d64e1595a3f95f937031e668f6b9a0fd53eb491a93641c4a539c6d702851b
|
|
4
|
+
data.tar.gz: c490032828ae6057ef5c4cfb8520c9942b7f0654e46262cc72a06aa3db078975
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c35f063fe1cbbf4f3b5867956acebe29f486a0fda4227d271fd3ae6e0b4c877329aad0d1d8e65d38b39cdca8abbb693da48bb6caab52fffe0e654489056b2df
|
|
7
|
+
data.tar.gz: c47560f824ba85fe4847c2992a46fbeb6c2504ecdf21de6cfd520fcc547b59de08dac53cb182113651efa316f47995f8a02cccc8ad268e5a00844eb7acc9ecfe
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Legion Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.187] - 2026-03-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `Legion::Extensions::Capability` Data.define struct for extension capability registration
|
|
7
|
+
- `Legion::Extensions::Catalog::Registry` in-memory capability registry with register, find, find_by_intent, for_mcp, for_override, find_by_mcp_name
|
|
8
|
+
- `register_capabilities` populates Catalog::Registry from extension runners at boot
|
|
9
|
+
- `unregister_capabilities` removes capabilities from Catalog on extension unload
|
|
10
|
+
- `Catalog::Registry.on_change` callback for notifying consumers on registry changes
|
|
11
|
+
|
|
3
12
|
## [1.4.186] - 2026-03-23
|
|
4
13
|
|
|
5
14
|
### Fixed
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
Capability = ::Data.define(
|
|
6
|
+
:name, :extension, :runner, :function,
|
|
7
|
+
:description, :parameters, :tags, :loaded_at
|
|
8
|
+
) do
|
|
9
|
+
def self.from_runner(extension:, runner:, function:, **opts)
|
|
10
|
+
canonical = "#{extension}:#{runner.to_s.gsub(/([A-Z])/, '_\1').sub(/^_/, '').downcase}:#{function}"
|
|
11
|
+
new(
|
|
12
|
+
name: canonical,
|
|
13
|
+
extension: extension,
|
|
14
|
+
runner: runner.to_s,
|
|
15
|
+
function: function.to_s,
|
|
16
|
+
description: opts[:description],
|
|
17
|
+
parameters: opts[:parameters] || {},
|
|
18
|
+
tags: Array(opts[:tags]),
|
|
19
|
+
loaded_at: Time.now
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def matches_intent?(text)
|
|
24
|
+
words = text.downcase.split(/\s+/)
|
|
25
|
+
searchable = [description, *tags, extension, runner, function]
|
|
26
|
+
.compact.join(' ').downcase
|
|
27
|
+
|
|
28
|
+
matching = words.count { |w| searchable.include?(w) }
|
|
29
|
+
matching.to_f / [words.length, 1].max >= 0.4
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_mcp_tool
|
|
33
|
+
snake_runner = runner.gsub(/([A-Z])/, '_\1').sub(/^_/, '').downcase
|
|
34
|
+
tool_name = "legion.#{extension.delete_prefix('lex-').tr('-', '_')}.#{snake_runner}.#{function}"
|
|
35
|
+
properties = (parameters || {}).transform_values do |v|
|
|
36
|
+
v.is_a?(Hash) ? v : { type: v.to_s }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
{
|
|
40
|
+
name: tool_name,
|
|
41
|
+
description: description || "#{extension} #{runner}##{function}",
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: properties,
|
|
45
|
+
required: parameters&.select { |_, v| v.is_a?(Hash) && v[:required] }&.keys&.map(&:to_s) || []
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Catalog
|
|
6
|
+
module Registry
|
|
7
|
+
@capabilities = []
|
|
8
|
+
@by_name = {}
|
|
9
|
+
@mutex = Mutex.new
|
|
10
|
+
@on_change_callbacks = []
|
|
11
|
+
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
def register(capability)
|
|
15
|
+
@mutex.synchronize do
|
|
16
|
+
return if @by_name.key?(capability.name)
|
|
17
|
+
|
|
18
|
+
@capabilities << capability
|
|
19
|
+
@by_name[capability.name] = capability
|
|
20
|
+
end
|
|
21
|
+
notify_change
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def unregister(name)
|
|
25
|
+
@mutex.synchronize do
|
|
26
|
+
cap = @by_name.delete(name)
|
|
27
|
+
@capabilities.delete(cap) if cap
|
|
28
|
+
return unless cap
|
|
29
|
+
end
|
|
30
|
+
notify_change
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unregister_extension(extension_name)
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
removed = @capabilities.select { |c| c.extension == extension_name }
|
|
36
|
+
removed.each do |cap|
|
|
37
|
+
@by_name.delete(cap.name)
|
|
38
|
+
@capabilities.delete(cap)
|
|
39
|
+
end
|
|
40
|
+
return if removed.empty?
|
|
41
|
+
end
|
|
42
|
+
notify_change
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def capabilities
|
|
46
|
+
@mutex.synchronize { @capabilities.dup.freeze }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def find(name:)
|
|
50
|
+
@mutex.synchronize { @by_name[name] }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def find_by_intent(text)
|
|
54
|
+
@mutex.synchronize do
|
|
55
|
+
@capabilities.select { |c| c.matches_intent?(text) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def for_mcp
|
|
60
|
+
@mutex.synchronize { @capabilities.dup }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def find_by_mcp_name(mcp_name)
|
|
64
|
+
@mutex.synchronize do
|
|
65
|
+
@capabilities.find { |cap| cap.to_mcp_tool[:name] == mcp_name }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def for_override(tool_name)
|
|
70
|
+
@mutex.synchronize do
|
|
71
|
+
normalized = tool_name.downcase.tr('-', '_')
|
|
72
|
+
@capabilities.find do |cap|
|
|
73
|
+
cap.function.downcase == normalized ||
|
|
74
|
+
cap.name.downcase.end_with?(normalized) ||
|
|
75
|
+
cap.tags.any? { |t| t.downcase == normalized }
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def count
|
|
81
|
+
@mutex.synchronize { @capabilities.length }
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def on_change(&block)
|
|
85
|
+
@mutex.synchronize { @on_change_callbacks << block }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def reset!
|
|
89
|
+
@mutex.synchronize do
|
|
90
|
+
@capabilities.clear
|
|
91
|
+
@by_name.clear
|
|
92
|
+
@on_change_callbacks.clear
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def notify_change
|
|
97
|
+
callbacks = @mutex.synchronize { @on_change_callbacks.dup }
|
|
98
|
+
callbacks.each do |cb|
|
|
99
|
+
cb.call
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
Legion::Logging.warn("Catalog::Registry on_change error: #{e.message}") if defined?(Legion::Logging)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private_class_method :notify_change
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/legion/extensions.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'legion/extensions/core'
|
|
4
|
+
require 'legion/extensions/capability'
|
|
4
5
|
require 'legion/extensions/catalog'
|
|
5
6
|
require 'legion/extensions/permissions'
|
|
6
7
|
require 'legion/runner'
|
|
@@ -45,7 +46,10 @@ module Legion
|
|
|
45
46
|
@timer_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
|
46
47
|
@poll_tasks.each { |task| task[:running_class].cancel if task[:running_class].respond_to?(:cancel) }
|
|
47
48
|
|
|
48
|
-
@loaded_extensions.each
|
|
49
|
+
@loaded_extensions.each do |name|
|
|
50
|
+
Catalog.transition(name, :stopped)
|
|
51
|
+
unregister_capabilities(name)
|
|
52
|
+
end
|
|
49
53
|
Legion::Logging.info 'Successfully shut down all actors'
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -158,6 +162,8 @@ module Legion
|
|
|
158
162
|
require 'legion/transport/messages/lex_register'
|
|
159
163
|
Legion::Transport::Messages::LexRegister.new(function: 'save', opts: extension.runners).publish
|
|
160
164
|
|
|
165
|
+
register_capabilities(entry[:gem_name], extension.runners) if extension.respond_to?(:runners)
|
|
166
|
+
|
|
161
167
|
if extension.respond_to?(:meta_actors) && extension.meta_actors.is_a?(Hash)
|
|
162
168
|
extension.meta_actors.each_value do |actor|
|
|
163
169
|
extension.log.debug("deferring meta actor: #{actor}") if has_logger
|
|
@@ -345,6 +351,36 @@ module Legion
|
|
|
345
351
|
|
|
346
352
|
public
|
|
347
353
|
|
|
354
|
+
def unregister_capabilities(gem_name)
|
|
355
|
+
Extensions::Catalog::Registry.unregister_extension(gem_name)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def register_capabilities(gem_name, runners)
|
|
359
|
+
runners.each_value do |runner_meta|
|
|
360
|
+
runner_name = runner_meta[:runner_name]
|
|
361
|
+
(runner_meta[:class_methods] || {}).each do |fn_name, fn_meta|
|
|
362
|
+
next if fn_name.to_s.start_with?('_')
|
|
363
|
+
|
|
364
|
+
params = {}
|
|
365
|
+
(fn_meta[:args] || []).each do |arg|
|
|
366
|
+
type, name = arg
|
|
367
|
+
params[name] = { type: :string, required: type == :keyreq }
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
cap = Extensions::Capability.from_runner(
|
|
371
|
+
extension: gem_name,
|
|
372
|
+
runner: runner_name.to_s.split('_').map(&:capitalize).join,
|
|
373
|
+
function: fn_name.to_s,
|
|
374
|
+
parameters: params,
|
|
375
|
+
tags: [gem_name.delete_prefix('lex-')]
|
|
376
|
+
)
|
|
377
|
+
Extensions::Catalog::Registry.register(cap)
|
|
378
|
+
end
|
|
379
|
+
rescue StandardError => e
|
|
380
|
+
Legion::Logging.warn("Catalog registration error for #{gem_name}: #{e.message}") if defined?(Legion::Logging)
|
|
381
|
+
end
|
|
382
|
+
end
|
|
383
|
+
|
|
348
384
|
def gem_load(entry)
|
|
349
385
|
gem_name = entry[:gem_name]
|
|
350
386
|
require_path = entry[:require_path]
|
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.187
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Esity
|
|
@@ -689,7 +689,9 @@ files:
|
|
|
689
689
|
- lib/legion/extensions/builders/hooks.rb
|
|
690
690
|
- lib/legion/extensions/builders/routes.rb
|
|
691
691
|
- lib/legion/extensions/builders/runners.rb
|
|
692
|
+
- lib/legion/extensions/capability.rb
|
|
692
693
|
- lib/legion/extensions/catalog.rb
|
|
694
|
+
- lib/legion/extensions/catalog/registry.rb
|
|
693
695
|
- lib/legion/extensions/core.rb
|
|
694
696
|
- lib/legion/extensions/data.rb
|
|
695
697
|
- lib/legion/extensions/data/migrator.rb
|