ruboty-ai_agent 0.1.0 → 0.2.0
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/.rubocop.yml +19 -0
- data/Gemfile +10 -3
- data/Rakefile +2 -0
- data/Steepfile +1 -0
- data/lib/ruboty/ai_agent/actions/add_ai_command.rb +16 -1
- data/lib/ruboty/ai_agent/actions/chat.rb +44 -16
- data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +20 -4
- data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +7 -1
- data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +1 -1
- data/lib/ruboty/ai_agent/chat_message.rb +7 -2
- data/lib/ruboty/ai_agent/chat_thread_messages.rb +40 -0
- data/lib/ruboty/ai_agent/commands/base.rb +11 -14
- data/lib/ruboty/ai_agent/commands/builtin_base.rb +39 -0
- data/lib/ruboty/ai_agent/commands/clear.rb +2 -14
- data/lib/ruboty/ai_agent/commands/compact.rb +4 -47
- data/lib/ruboty/ai_agent/commands/prompt_command.rb +61 -0
- data/lib/ruboty/ai_agent/commands/usage.rb +2 -14
- data/lib/ruboty/ai_agent/commands.rb +3 -1
- data/lib/ruboty/ai_agent/database/query_methods.rb +31 -6
- data/lib/ruboty/ai_agent/database.rb +2 -1
- data/lib/ruboty/ai_agent/http_mcp_client.rb +5 -2
- data/lib/ruboty/ai_agent/llm/openai.rb +5 -5
- data/lib/ruboty/ai_agent/mcp_clients.rb +6 -0
- data/lib/ruboty/ai_agent/mcp_configuration.rb +3 -2
- data/lib/ruboty/ai_agent/prompt_command_definition.rb +17 -0
- data/lib/ruboty/ai_agent/record_set.rb +9 -5
- data/lib/ruboty/ai_agent/recordable.rb +11 -9
- data/lib/ruboty/ai_agent/token_usage.rb +10 -0
- data/lib/ruboty/ai_agent/user.rb +5 -0
- data/lib/ruboty/ai_agent/user_mcp_caches.rb +2 -2
- data/lib/ruboty/ai_agent/user_mcp_client.rb +2 -2
- data/lib/ruboty/ai_agent/user_prompt_command_definitions.rb +17 -0
- data/lib/ruboty/ai_agent/version.rb +1 -1
- data/lib/ruboty/ai_agent.rb +11 -0
- data/lib/ruboty/handlers/ai_agent.rb +27 -15
- data/rbs_collection.yaml +1 -0
- data/ruboty-ai_agent.gemspec +2 -6
- data/script/clean-orphaned-rbs.rb +105 -0
- data/script/generate-concern-rbs.rb +5 -5
- data/script/generate-data-rbs.rb +3 -5
- data/script/generate-memorized-ivar-rbs.rb +6 -11
- data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/actions/chat.rbs +6 -0
- data/sig/generated/ruboty/ai_agent/chat_message.rbs +5 -2
- data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +14 -0
- data/sig/generated/ruboty/ai_agent/commands/base.rbs +8 -20
- data/sig/generated/ruboty/ai_agent/commands/builtin_base.rbs +40 -0
- data/sig/generated/ruboty/ai_agent/commands/clear.rbs +2 -10
- data/sig/generated/ruboty/ai_agent/commands/compact.rbs +2 -20
- data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +27 -0
- data/sig/generated/ruboty/ai_agent/commands/usage.rbs +2 -10
- data/sig/generated/ruboty/ai_agent/commands.rbs +2 -2
- data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +18 -12
- data/sig/generated/ruboty/ai_agent/database.rbs +3 -1
- data/sig/generated/ruboty/ai_agent/llm/openai.rbs +3 -3
- data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +4 -2
- data/sig/generated/ruboty/ai_agent/prompt_command_definition.rbs +23 -0
- data/sig/generated/ruboty/ai_agent/record_set.rbs +11 -9
- data/sig/generated/ruboty/ai_agent/recordable.rbs +6 -6
- data/sig/generated/ruboty/ai_agent/token_usage.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/user.rbs +4 -0
- data/sig/generated/ruboty/ai_agent/user_prompt_command_definitions.rbs +12 -0
- data/sig/generated/ruboty/handlers/ai_agent.rbs +12 -12
- data/sig/generated-by-scripts/concerns.rbs +5 -0
- data/sig/generated-by-scripts/memorized_ivars.rbs +9 -0
- metadata +11 -57
@@ -7,27 +7,49 @@ module Ruboty
|
|
7
7
|
# interface _WithData
|
8
8
|
# def data: -> Hash[keynable, untyped]
|
9
9
|
# end
|
10
|
+
#
|
11
|
+
# type query_key = keynable | Symbol | nil
|
10
12
|
|
11
13
|
# @rbs module-self _WithData
|
12
14
|
module QueryMethods
|
13
|
-
# @rbs
|
15
|
+
# @rbs keys: Array[query_key]
|
16
|
+
# @rbs return: Array[keynable]
|
17
|
+
def self.keys_to_keynable(keys)
|
18
|
+
keys.map do |k|
|
19
|
+
case k
|
20
|
+
when String, Symbol
|
21
|
+
k.to_s
|
22
|
+
when Integer
|
23
|
+
k
|
24
|
+
when NilClass
|
25
|
+
nil
|
26
|
+
else
|
27
|
+
raise ArgumentError, "Invalid key type: #{k.class}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# @rbs *keys: query_key
|
14
33
|
# @rbs return: untyped
|
15
34
|
def fetch(*keys)
|
35
|
+
keys = QueryMethods.keys_to_keynable(keys)
|
16
36
|
item = data.dig(*keys)
|
17
37
|
|
18
38
|
Recordable.instantiate_recursively(item)
|
19
39
|
end
|
20
40
|
|
21
|
-
# @rbs *keys:
|
41
|
+
# @rbs *keys: query_key
|
22
42
|
# @rbs return: Integer
|
23
43
|
def len(*keys)
|
44
|
+
keys = QueryMethods.keys_to_keynable(keys)
|
24
45
|
item = data.dig(*keys)
|
25
46
|
item.respond_to?(:length) ? item.length : 0
|
26
47
|
end
|
27
48
|
|
28
|
-
# @rbs *keys:
|
49
|
+
# @rbs *keys: query_key
|
29
50
|
# @rbs return: void
|
30
51
|
def delete(*keys)
|
52
|
+
keys = QueryMethods.keys_to_keynable(keys)
|
31
53
|
namespace_keys = keys[0..-2] || []
|
32
54
|
key = keys[-1]
|
33
55
|
|
@@ -40,9 +62,10 @@ module Ruboty
|
|
40
62
|
end
|
41
63
|
end
|
42
64
|
|
43
|
-
# @rbs *keys:
|
65
|
+
# @rbs *keys: query_key
|
44
66
|
# @rbs return: Array[keynable]
|
45
67
|
def keys(*keys)
|
68
|
+
keys = QueryMethods.keys_to_keynable(keys)
|
46
69
|
namespace = keys.empty? ? data : data.dig(*keys) #: top
|
47
70
|
case namespace
|
48
71
|
when Hash
|
@@ -54,9 +77,10 @@ module Ruboty
|
|
54
77
|
end
|
55
78
|
end
|
56
79
|
|
57
|
-
# @rbs *keys:
|
80
|
+
# @rbs *keys: query_key
|
58
81
|
# @rbs return: boolish
|
59
82
|
def key?(*keys)
|
83
|
+
keys = QueryMethods.keys_to_keynable(keys)
|
60
84
|
namespace_keys = keys[0..-2] || []
|
61
85
|
key = keys[-1]
|
62
86
|
|
@@ -64,10 +88,11 @@ module Ruboty
|
|
64
88
|
namespace&.key?(key)
|
65
89
|
end
|
66
90
|
|
67
|
-
# @rbs at: Array[
|
91
|
+
# @rbs at: Array[query_key]
|
68
92
|
# @rbs value: untyped
|
69
93
|
# @rbs return: void
|
70
94
|
def store(value, at:)
|
95
|
+
at = QueryMethods.keys_to_keynable(at)
|
71
96
|
namespace_keys = at[0..-2] || []
|
72
97
|
key = at[-1]
|
73
98
|
|
@@ -5,7 +5,7 @@ module Ruboty
|
|
5
5
|
# Memorize and retrieve information using Ruboty's brain.
|
6
6
|
class Database
|
7
7
|
# @rbs!
|
8
|
-
# type keynable =
|
8
|
+
# type keynable = String | Integer
|
9
9
|
|
10
10
|
autoload :QueryMethods, 'ruboty/ai_agent/database/query_methods'
|
11
11
|
|
@@ -32,6 +32,7 @@ module Ruboty
|
|
32
32
|
ChatThread.find_or_create(database: self, id: id)
|
33
33
|
end
|
34
34
|
|
35
|
+
# @rbs %a{memorized}
|
35
36
|
def global_settings #: GlobalSettings
|
36
37
|
@global_settings ||= GlobalSettings.find_or_create(database: self)
|
37
38
|
end
|
@@ -51,7 +51,7 @@ module Ruboty
|
|
51
51
|
|
52
52
|
# @rbs name: String
|
53
53
|
# @rbs &block: ? (Hash[String, untyped]) -> void
|
54
|
-
def call_tool(name, arguments = {}, &
|
54
|
+
def call_tool(name, arguments = {}, &)
|
55
55
|
ensure_initialized
|
56
56
|
results = send_request(
|
57
57
|
method: 'tools/call',
|
@@ -59,7 +59,7 @@ module Ruboty
|
|
59
59
|
name: name,
|
60
60
|
arguments: arguments
|
61
61
|
},
|
62
|
-
&
|
62
|
+
&
|
63
63
|
)
|
64
64
|
|
65
65
|
results.flat_map { |res| res.dig('result', 'content') || [] }
|
@@ -166,6 +166,9 @@ module Ruboty
|
|
166
166
|
else
|
167
167
|
handle_response(response, &block)
|
168
168
|
end
|
169
|
+
rescue Net::HTTPExceptions, SystemCallError => e
|
170
|
+
error_message = e.is_a?(StandardError) ? e.message : "Unknown error (#{e.class.name})"
|
171
|
+
raise Error, "HTTP request failed: #{error_message}"
|
169
172
|
end
|
170
173
|
|
171
174
|
# @rbs response: Net::HTTPResponse
|
@@ -11,11 +11,11 @@ module Ruboty
|
|
11
11
|
attr_reader :client #: OpenAI::Client
|
12
12
|
attr_reader :model #: String
|
13
13
|
|
14
|
-
# @rbs client: OpenAI::Client
|
15
|
-
# @rbs model: String
|
16
|
-
def initialize(client
|
17
|
-
@client = client
|
18
|
-
@model = model
|
14
|
+
# @rbs ?client: OpenAI::Client
|
15
|
+
# @rbs ?model: String
|
16
|
+
def initialize(client: nil, model: nil)
|
17
|
+
@client = client || ::OpenAI::Client.new(api_key: ENV.fetch('OPENAI_API_KEY', nil))
|
18
|
+
@model = model || ENV.fetch('OPENAI_MODEL', 'gpt-5-nano')
|
19
19
|
end
|
20
20
|
|
21
21
|
# @rbs %a{memorized}
|
@@ -25,6 +25,9 @@ module Ruboty
|
|
25
25
|
client.call_tool(tool_def['name'], params).to_json
|
26
26
|
end
|
27
27
|
end
|
28
|
+
rescue HttpMcpClient::Error => e
|
29
|
+
warn "Failed to list tools for MCP client: #{e.message}"
|
30
|
+
[]
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
@@ -37,6 +40,9 @@ module Ruboty
|
|
37
40
|
return mcp_client.call_tool(function_name, arguments) if tools.any? { |t| t['name'] == function_name }
|
38
41
|
end
|
39
42
|
nil
|
43
|
+
rescue HttpMcpClient::Error => e
|
44
|
+
warn "Failed to execute tool '#{function_name}': #{e.message}"
|
45
|
+
nil
|
40
46
|
end
|
41
47
|
|
42
48
|
# @rbs return: bool
|
@@ -4,6 +4,7 @@ module Ruboty
|
|
4
4
|
module AiAgent
|
5
5
|
# @rbs!
|
6
6
|
# type transports = :http | :websocket
|
7
|
+
# type transports_str = "http" | "websocket"
|
7
8
|
|
8
9
|
McpConfiguration = Data.define(
|
9
10
|
:name, #: String
|
@@ -17,12 +18,12 @@ module Ruboty
|
|
17
18
|
include Recordable
|
18
19
|
|
19
20
|
# @rbs name: String
|
20
|
-
# @rbs transport: transports
|
21
|
+
# @rbs transport: transports | transports_str
|
21
22
|
# @rbs url: String
|
22
23
|
# @rbs headers: Hash[String, String]?
|
23
24
|
def initialize(name:, transport:, url:, headers: {})
|
24
25
|
# No superclass method `initialize` in RBS.
|
25
|
-
super(name:, transport
|
26
|
+
super(name:, transport: transport.to_sym, headers:, url:) # steep:ignore UnexpectedKeywordArgument
|
26
27
|
end
|
27
28
|
|
28
29
|
register_record_type :mcp_configuration
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
PromptCommandDefinition = Data.define(
|
6
|
+
:name, #: String
|
7
|
+
:prompt #: String
|
8
|
+
)
|
9
|
+
|
10
|
+
# User-defined command model
|
11
|
+
class PromptCommandDefinition
|
12
|
+
include Recordable
|
13
|
+
|
14
|
+
register_record_type :user_defined_command
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -11,10 +11,14 @@ module Ruboty
|
|
11
11
|
@database = database
|
12
12
|
end
|
13
13
|
|
14
|
-
def namespace_keys #: Array[Database::
|
14
|
+
def namespace_keys #: Array[Database::query_key]
|
15
15
|
raise NotImplementedError, 'Subclasses must implement the namespace_keys method'
|
16
16
|
end
|
17
17
|
|
18
|
+
def empty? #: boolish
|
19
|
+
length.zero?
|
20
|
+
end
|
21
|
+
|
18
22
|
def length #: Integer
|
19
23
|
database.len(*namespace_keys)
|
20
24
|
end
|
@@ -38,26 +42,26 @@ module Ruboty
|
|
38
42
|
database.keys(*namespace_keys)
|
39
43
|
end
|
40
44
|
|
41
|
-
# @rbs key: Database::
|
45
|
+
# @rbs key: Database::query_key
|
42
46
|
# @rbs return: Record | nil
|
43
47
|
def fetch(key)
|
44
48
|
database.fetch(*namespace_keys, key)
|
45
49
|
end
|
46
50
|
|
47
|
-
# @rbs key: Database::
|
51
|
+
# @rbs key: Database::query_key
|
48
52
|
# @rbs record: Record
|
49
53
|
# @rbs return: void
|
50
54
|
def store(record, key:)
|
51
55
|
database.store(record, at: [*namespace_keys, key])
|
52
56
|
end
|
53
57
|
|
54
|
-
# @rbs key: Database::
|
58
|
+
# @rbs key: Database::query_key
|
55
59
|
# @rbs return: void
|
56
60
|
def remove(key)
|
57
61
|
database.delete(*namespace_keys, key)
|
58
62
|
end
|
59
63
|
|
60
|
-
# @rbs key: Database::
|
64
|
+
# @rbs key: Database::query_key
|
61
65
|
# @rbs return: boolish
|
62
66
|
def key?(key)
|
63
67
|
database.key?(*namespace_keys, key)
|
@@ -4,7 +4,7 @@ module Ruboty
|
|
4
4
|
module AiAgent
|
5
5
|
# @rbs!
|
6
6
|
# interface _WithToH
|
7
|
-
# def to_h: () -> Hash[
|
7
|
+
# def to_h: () -> Hash[Symbol, untyped]
|
8
8
|
# end
|
9
9
|
|
10
10
|
# Convertable between Hash and Recordable bidirectionally.
|
@@ -22,12 +22,12 @@ module Ruboty
|
|
22
22
|
@record_types ||= {}
|
23
23
|
end
|
24
24
|
|
25
|
-
# @rbs hash: Hash[Symbol, untyped]?
|
25
|
+
# @rbs hash: Hash[Symbol | String, untyped]?
|
26
26
|
# @rbs return: bool
|
27
27
|
def convertable?(hash)
|
28
28
|
return false unless hash.is_a?(Hash)
|
29
29
|
|
30
|
-
type = hash[:record_type]
|
30
|
+
type = (hash[:record_type] || hash['record_type'])&.to_sym
|
31
31
|
type && record_types.include?(type)
|
32
32
|
end
|
33
33
|
|
@@ -54,9 +54,9 @@ module Ruboty
|
|
54
54
|
def hashify_recursively(value)
|
55
55
|
case value
|
56
56
|
when Recordable
|
57
|
-
hashify_recursively(value
|
57
|
+
hashify_recursively(record_to_hash(value))
|
58
58
|
when Hash
|
59
|
-
value.transform_values { |v| hashify_recursively(v) }
|
59
|
+
value.transform_values { |v| hashify_recursively(v) }.transform_keys(&:to_s)
|
60
60
|
when Array
|
61
61
|
value.map { |v| hashify_recursively(v) }
|
62
62
|
else
|
@@ -67,13 +67,15 @@ module Ruboty
|
|
67
67
|
# @rbs record: Recordable
|
68
68
|
# @rbs return: Hash[Database::keynable, untyped]
|
69
69
|
def record_to_hash(record)
|
70
|
-
record.to_h
|
70
|
+
record.to_h.transform_keys(&:to_s)
|
71
71
|
end
|
72
72
|
|
73
|
-
# @rbs hash: Hash[Symbol, untyped]
|
73
|
+
# @rbs hash: Hash[Symbol | String, untyped]
|
74
74
|
# @rbs return: Recordable
|
75
75
|
def record_from_hash(hash)
|
76
|
-
|
76
|
+
hash = hash.transform_keys(&:to_sym)
|
77
|
+
|
78
|
+
type = hash[:record_type]&.to_sym
|
77
79
|
klass = record_types[type]
|
78
80
|
raise "Unknown record type: #{type}" unless klass
|
79
81
|
|
@@ -98,7 +100,7 @@ module Ruboty
|
|
98
100
|
|
99
101
|
# @rbs module-self Recordable::ClassMethods.instance
|
100
102
|
module PrependMethods
|
101
|
-
def to_h #: Hash[
|
103
|
+
def to_h #: Hash[Symbol, untyped]
|
102
104
|
{
|
103
105
|
record_type: record_type,
|
104
106
|
**super
|
@@ -32,6 +32,16 @@ module Ruboty
|
|
32
32
|
(total_tokens.to_f / token_limit * 100).round(2).to_f
|
33
33
|
end
|
34
34
|
|
35
|
+
# Check if usage percentage exceeds auto compact threshold
|
36
|
+
# @rbs return: bool
|
37
|
+
def over_auto_compact_threshold?
|
38
|
+
percentage = usage_percentage
|
39
|
+
return false unless percentage
|
40
|
+
|
41
|
+
threshold = ENV.fetch('AUTO_COMPACT_THRESHOLD', 80).to_f
|
42
|
+
percentage >= threshold
|
43
|
+
end
|
44
|
+
|
35
45
|
def to_h #: Hash[Symbol, untyped]
|
36
46
|
{
|
37
47
|
prompt_tokens:,
|
data/lib/ruboty/ai_agent/user.rb
CHANGED
@@ -47,6 +47,11 @@ module Ruboty
|
|
47
47
|
def mcp_tools_caches #: UserMcpToolsCaches
|
48
48
|
@mcp_tools_caches ||= UserMcpToolsCaches.new(database: database, user_id: id)
|
49
49
|
end
|
50
|
+
|
51
|
+
# @rbs %a{memorized}
|
52
|
+
def prompt_command_definitions #: UserPromptCommandDefinitions
|
53
|
+
@prompt_command_definitions ||= UserPromptCommandDefinitions.new(database: database, user_id: id)
|
54
|
+
end
|
50
55
|
end
|
51
56
|
end
|
52
57
|
end
|
@@ -57,12 +57,12 @@ module Ruboty
|
|
57
57
|
|
58
58
|
# @rbs override
|
59
59
|
def all
|
60
|
-
super
|
60
|
+
super.reject { |(_key, cache)| cache.expired? }
|
61
61
|
end
|
62
62
|
|
63
63
|
# @rbs override
|
64
64
|
def fetch(key)
|
65
|
-
cache = super
|
65
|
+
cache = super
|
66
66
|
|
67
67
|
if cache&.expired?
|
68
68
|
remove(key)
|
@@ -26,8 +26,8 @@ module Ruboty
|
|
26
26
|
# @rbs arguments: Hash[String, untyped]
|
27
27
|
# @rbs &block: ? (Hash[String, untyped]) -> void
|
28
28
|
# @rbs return: untyped
|
29
|
-
def call_tool(name, arguments = {}, &
|
30
|
-
mcp_client.call_tool(name, arguments, &
|
29
|
+
def call_tool(name, arguments = {}, &)
|
30
|
+
mcp_client.call_tool(name, arguments, &)
|
31
31
|
end
|
32
32
|
|
33
33
|
# @rbs return: untyped
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ruboty
|
4
|
+
module AiAgent
|
5
|
+
# User-defined commands record set
|
6
|
+
class UserPromptCommandDefinitions < UserAssociations #[PromptCommandDefinition]
|
7
|
+
self.association_key = :user_prompt_command_definitions
|
8
|
+
|
9
|
+
# @rbs command: PromptCommandDefinition
|
10
|
+
# @rbs return: PromptCommandDefinition
|
11
|
+
def add(command)
|
12
|
+
store(command, key: command.name)
|
13
|
+
command
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/ruboty/ai_agent.rb
CHANGED
@@ -25,6 +25,7 @@ module Ruboty
|
|
25
25
|
autoload :McpClient, 'ruboty/ai_agent/mcp_client'
|
26
26
|
autoload :McpClients, 'ruboty/ai_agent/mcp_clients'
|
27
27
|
autoload :McpConfiguration, 'ruboty/ai_agent/mcp_configuration'
|
28
|
+
autoload :PromptCommandDefinition, 'ruboty/ai_agent/prompt_command_definition'
|
28
29
|
autoload :Recordable, 'ruboty/ai_agent/recordable'
|
29
30
|
autoload :RecordSet, 'ruboty/ai_agent/record_set'
|
30
31
|
autoload :TokenUsage, 'ruboty/ai_agent/token_usage'
|
@@ -36,5 +37,15 @@ module Ruboty
|
|
36
37
|
autoload :UserMcpClient, 'ruboty/ai_agent/user_mcp_client'
|
37
38
|
autoload :UserMcpConfigurations, 'ruboty/ai_agent/user_mcp_configurations'
|
38
39
|
autoload :UserMcpToolsCaches, 'ruboty/ai_agent/user_mcp_tools_caches'
|
40
|
+
autoload :UserPromptCommandDefinitions, 'ruboty/ai_agent/user_prompt_command_definitions'
|
41
|
+
|
42
|
+
# Ensure all recordables are loaded
|
43
|
+
[
|
44
|
+
CachedValue,
|
45
|
+
ChatMessage,
|
46
|
+
McpConfiguration,
|
47
|
+
PromptCommandDefinition,
|
48
|
+
TokenUsage
|
49
|
+
]
|
39
50
|
end
|
40
51
|
end
|
@@ -20,64 +20,76 @@ module Ruboty
|
|
20
20
|
on(/remove mcp (?<name>\S+)/, name: 'remove_mcp', description: 'Remove the specified MCP server')
|
21
21
|
on(/list mcps?/, name: 'list_mcp', description: 'List configured MCP servers')
|
22
22
|
|
23
|
-
on(/set
|
23
|
+
on(/set (?:(?<scope>user|global) )?system prompt "(?<prompt>.+?)"/, name: 'set_system_prompt', description: 'Set system prompt')
|
24
24
|
on(/show system prompt/, name: 'show_system_prompt', description: 'Show system prompt')
|
25
25
|
|
26
26
|
on(/add ai memory "(?<prompt>.+)"/, name: 'add_ai_memory', description: 'Add a new AI memory')
|
27
27
|
on(/remove ai memory (?<index>\d+)/, name: 'remove_ai_memory', description: 'Remove the specified AI memory')
|
28
28
|
on(/list ai memor(?:y|ies)/, name: 'list_mcp', description: "List AI's memories")
|
29
29
|
|
30
|
-
on(%r{add ai command /(?<name>\S+)\s+
|
31
|
-
|
30
|
+
on(%r{add ai command /(?<name>\S+)\s+(?<prompt>.+)}, name: 'add_ai_command',
|
31
|
+
description: 'Add a new AI command')
|
32
32
|
on(%r{remove ai command /(?<name>\S+)}, name: 'remove_ai_command', description: 'Remove the specified AI command')
|
33
33
|
on(/list ai commands?/, name: 'list_ai_commands', description: 'List AI commands')
|
34
34
|
|
35
|
-
def chat(message)
|
35
|
+
def chat(message) #: true
|
36
36
|
Ruboty::AiAgent::Actions::Chat.call(message)
|
37
|
+
true
|
37
38
|
end
|
38
39
|
|
39
|
-
def add_mcp(message)
|
40
|
+
def add_mcp(message) #: true
|
40
41
|
Ruboty::AiAgent::Actions::AddMcp.call(message)
|
42
|
+
true
|
41
43
|
end
|
42
44
|
|
43
|
-
def remove_mcp(message)
|
45
|
+
def remove_mcp(message) #: true
|
44
46
|
Ruboty::AiAgent::Actions::RemoveMcp.call(message)
|
47
|
+
true
|
45
48
|
end
|
46
49
|
|
47
|
-
def list_mcp(message)
|
50
|
+
def list_mcp(message) #: true
|
48
51
|
Ruboty::AiAgent::Actions::ListMcp.call(message)
|
52
|
+
true
|
49
53
|
end
|
50
54
|
|
51
|
-
def set_system_prompt(message)
|
55
|
+
def set_system_prompt(message) #: true
|
52
56
|
Ruboty::AiAgent::Actions::SetSystemPrompt.call(message)
|
57
|
+
true
|
53
58
|
end
|
54
59
|
|
55
|
-
def show_system_prompt(message)
|
60
|
+
def show_system_prompt(message) #: true
|
56
61
|
Ruboty::AiAgent::Actions::ShowSystemPrompt.call(message)
|
62
|
+
true
|
57
63
|
end
|
58
64
|
|
59
|
-
def add_ai_memory(message)
|
65
|
+
def add_ai_memory(message) #: true
|
60
66
|
Ruboty::AiAgent::Actions::AddAiMemory.call(message)
|
67
|
+
true
|
61
68
|
end
|
62
69
|
|
63
|
-
def remove_ai_memory(message)
|
70
|
+
def remove_ai_memory(message) #: true
|
64
71
|
Ruboty::AiAgent::Actions::RemoveAiMemory.call(message)
|
72
|
+
true
|
65
73
|
end
|
66
74
|
|
67
|
-
def list_ai_memories(message)
|
75
|
+
def list_ai_memories(message) #: true
|
68
76
|
Ruboty::AiAgent::Actions::ListAiMemories.call(message)
|
77
|
+
true
|
69
78
|
end
|
70
79
|
|
71
|
-
def add_ai_command(message)
|
80
|
+
def add_ai_command(message) #: true
|
72
81
|
Ruboty::AiAgent::Actions::AddAiCommand.call(message)
|
82
|
+
true
|
73
83
|
end
|
74
84
|
|
75
|
-
def remove_ai_command(message)
|
85
|
+
def remove_ai_command(message) #: true
|
76
86
|
Ruboty::AiAgent::Actions::RemoveAiCommand.call(message)
|
87
|
+
true
|
77
88
|
end
|
78
89
|
|
79
|
-
def list_ai_commands(message)
|
90
|
+
def list_ai_commands(message) #: true
|
80
91
|
Ruboty::AiAgent::Actions::ListAiCommands.call(message)
|
92
|
+
true
|
81
93
|
end
|
82
94
|
end
|
83
95
|
end
|
data/rbs_collection.yaml
CHANGED
data/ruboty-ai_agent.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.metadata['changelog_uri'] = 'https://github.com/tomoasleep/ruboty-ai_agent/blob/main/CHANGELOG.md'
|
27
27
|
else
|
28
28
|
raise 'RubyGems 2.0 or newer is required to protect against ' \
|
29
|
-
|
29
|
+
'public gem pushes.'
|
30
30
|
end
|
31
31
|
|
32
32
|
# Specify which files should be added to the gem when it is released.
|
@@ -38,12 +38,8 @@ Gem::Specification.new do |spec|
|
|
38
38
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
39
39
|
spec.require_paths = ['lib']
|
40
40
|
|
41
|
-
spec.add_development_dependency 'bundler', '~> 2'
|
42
|
-
spec.add_development_dependency 'rake', '~> 13'
|
43
|
-
spec.add_development_dependency 'rspec', '~> 3.0'
|
44
|
-
spec.add_development_dependency 'webmock', '~> 3.0'
|
45
|
-
|
46
41
|
spec.add_dependency 'event_stream_parser', '~> 1.0'
|
47
42
|
spec.add_dependency 'openai', '~> 0.22.0'
|
48
43
|
spec.add_dependency 'ruboty', '~> 1.3'
|
44
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
49
45
|
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Clean orphaned RBS files from sig/generated
|
5
|
+
#
|
6
|
+
# This script removes RBS files from sig/generated directory that don't have
|
7
|
+
# corresponding Ruby files in the project.
|
8
|
+
|
9
|
+
require 'fileutils'
|
10
|
+
require 'pathname'
|
11
|
+
|
12
|
+
# Remove orphaned RBS files by `rbs-inline`
|
13
|
+
class OrphanedRbsCleaner
|
14
|
+
def initialize(project_root: Dir.pwd)
|
15
|
+
@project_root = Pathname.new(project_root)
|
16
|
+
@sig_generated_dir = @project_root / 'sig' / 'generated'
|
17
|
+
@removed_files = []
|
18
|
+
@kept_files = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def run(dry_run: false)
|
22
|
+
return unless @sig_generated_dir.exist?
|
23
|
+
|
24
|
+
puts "Scanning for orphaned RBS files in #{@sig_generated_dir}..."
|
25
|
+
|
26
|
+
rbs_files = find_rbs_files
|
27
|
+
puts "Found #{rbs_files.size} RBS files to check"
|
28
|
+
|
29
|
+
rbs_files.each do |rbs_file|
|
30
|
+
corresponding_ruby_file = find_corresponding_ruby_file(rbs_file)
|
31
|
+
|
32
|
+
if corresponding_ruby_file&.exist?
|
33
|
+
@kept_files << rbs_file
|
34
|
+
else
|
35
|
+
@removed_files << rbs_file
|
36
|
+
if dry_run
|
37
|
+
puts "Would remove: #{rbs_file.relative_path_from(@project_root)}"
|
38
|
+
else
|
39
|
+
puts "Removing: #{rbs_file.relative_path_from(@project_root)}"
|
40
|
+
rbs_file.delete
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
cleanup_empty_directories unless dry_run
|
46
|
+
|
47
|
+
print_summary
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def find_rbs_files
|
53
|
+
@sig_generated_dir.glob('**/*.rbs').sort
|
54
|
+
end
|
55
|
+
|
56
|
+
def find_corresponding_ruby_file(rbs_file)
|
57
|
+
# Convert sig/generated/path/to/file.rbs to possible Ruby file locations
|
58
|
+
relative_path = rbs_file.relative_path_from(@sig_generated_dir)
|
59
|
+
ruby_file_name = relative_path.sub_ext('.rb')
|
60
|
+
|
61
|
+
# Check common Ruby source directories
|
62
|
+
possible_locations = [
|
63
|
+
@project_root / 'lib' / ruby_file_name
|
64
|
+
]
|
65
|
+
|
66
|
+
possible_locations.find(&:exist?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def cleanup_empty_directories
|
70
|
+
# Remove empty directories in sig/generated
|
71
|
+
@sig_generated_dir.find do |path|
|
72
|
+
next unless path.directory?
|
73
|
+
next if path == @sig_generated_dir
|
74
|
+
|
75
|
+
begin
|
76
|
+
path.rmdir if path.empty?
|
77
|
+
rescue Errno::ENOTEMPTY
|
78
|
+
# Directory is not empty, skip
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def print_summary
|
84
|
+
puts "\nSummary:"
|
85
|
+
puts " Kept files: #{@kept_files.size}"
|
86
|
+
puts " Removed files: #{@removed_files.size}"
|
87
|
+
|
88
|
+
return unless @removed_files.any?
|
89
|
+
|
90
|
+
puts "\nRemoved files:"
|
91
|
+
@removed_files.each do |file|
|
92
|
+
puts " - #{file.relative_path_from(@project_root)}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Script execution
|
98
|
+
if __FILE__ == $PROGRAM_NAME
|
99
|
+
dry_run = ARGV.include?('--dry-run')
|
100
|
+
|
101
|
+
puts 'Running in dry-run mode (no files will be deleted)' if dry_run
|
102
|
+
|
103
|
+
cleaner = OrphanedRbsCleaner.new
|
104
|
+
cleaner.run(dry_run: dry_run)
|
105
|
+
end
|