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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +19 -0
  3. data/Gemfile +10 -3
  4. data/Rakefile +2 -0
  5. data/Steepfile +1 -0
  6. data/lib/ruboty/ai_agent/actions/add_ai_command.rb +16 -1
  7. data/lib/ruboty/ai_agent/actions/chat.rb +44 -16
  8. data/lib/ruboty/ai_agent/actions/list_ai_commands.rb +20 -4
  9. data/lib/ruboty/ai_agent/actions/remove_ai_command.rb +7 -1
  10. data/lib/ruboty/ai_agent/actions/show_system_prompt.rb +1 -1
  11. data/lib/ruboty/ai_agent/chat_message.rb +7 -2
  12. data/lib/ruboty/ai_agent/chat_thread_messages.rb +40 -0
  13. data/lib/ruboty/ai_agent/commands/base.rb +11 -14
  14. data/lib/ruboty/ai_agent/commands/builtin_base.rb +39 -0
  15. data/lib/ruboty/ai_agent/commands/clear.rb +2 -14
  16. data/lib/ruboty/ai_agent/commands/compact.rb +4 -47
  17. data/lib/ruboty/ai_agent/commands/prompt_command.rb +61 -0
  18. data/lib/ruboty/ai_agent/commands/usage.rb +2 -14
  19. data/lib/ruboty/ai_agent/commands.rb +3 -1
  20. data/lib/ruboty/ai_agent/database/query_methods.rb +31 -6
  21. data/lib/ruboty/ai_agent/database.rb +2 -1
  22. data/lib/ruboty/ai_agent/http_mcp_client.rb +5 -2
  23. data/lib/ruboty/ai_agent/llm/openai.rb +5 -5
  24. data/lib/ruboty/ai_agent/mcp_clients.rb +6 -0
  25. data/lib/ruboty/ai_agent/mcp_configuration.rb +3 -2
  26. data/lib/ruboty/ai_agent/prompt_command_definition.rb +17 -0
  27. data/lib/ruboty/ai_agent/record_set.rb +9 -5
  28. data/lib/ruboty/ai_agent/recordable.rb +11 -9
  29. data/lib/ruboty/ai_agent/token_usage.rb +10 -0
  30. data/lib/ruboty/ai_agent/user.rb +5 -0
  31. data/lib/ruboty/ai_agent/user_mcp_caches.rb +2 -2
  32. data/lib/ruboty/ai_agent/user_mcp_client.rb +2 -2
  33. data/lib/ruboty/ai_agent/user_prompt_command_definitions.rb +17 -0
  34. data/lib/ruboty/ai_agent/version.rb +1 -1
  35. data/lib/ruboty/ai_agent.rb +11 -0
  36. data/lib/ruboty/handlers/ai_agent.rb +27 -15
  37. data/rbs_collection.yaml +1 -0
  38. data/ruboty-ai_agent.gemspec +2 -6
  39. data/script/clean-orphaned-rbs.rb +105 -0
  40. data/script/generate-concern-rbs.rb +5 -5
  41. data/script/generate-data-rbs.rb +3 -5
  42. data/script/generate-memorized-ivar-rbs.rb +6 -11
  43. data/sig/generated/ruboty/ai_agent/actions/add_ai_command.rbs +4 -0
  44. data/sig/generated/ruboty/ai_agent/actions/chat.rbs +6 -0
  45. data/sig/generated/ruboty/ai_agent/chat_message.rbs +5 -2
  46. data/sig/generated/ruboty/ai_agent/chat_thread_messages.rbs +14 -0
  47. data/sig/generated/ruboty/ai_agent/commands/base.rbs +8 -20
  48. data/sig/generated/ruboty/ai_agent/commands/builtin_base.rbs +40 -0
  49. data/sig/generated/ruboty/ai_agent/commands/clear.rbs +2 -10
  50. data/sig/generated/ruboty/ai_agent/commands/compact.rbs +2 -20
  51. data/sig/generated/ruboty/ai_agent/commands/prompt_command.rbs +27 -0
  52. data/sig/generated/ruboty/ai_agent/commands/usage.rbs +2 -10
  53. data/sig/generated/ruboty/ai_agent/commands.rbs +2 -2
  54. data/sig/generated/ruboty/ai_agent/database/query_methods.rbs +18 -12
  55. data/sig/generated/ruboty/ai_agent/database.rbs +3 -1
  56. data/sig/generated/ruboty/ai_agent/llm/openai.rbs +3 -3
  57. data/sig/generated/ruboty/ai_agent/mcp_configuration.rbs +4 -2
  58. data/sig/generated/ruboty/ai_agent/prompt_command_definition.rbs +23 -0
  59. data/sig/generated/ruboty/ai_agent/record_set.rbs +11 -9
  60. data/sig/generated/ruboty/ai_agent/recordable.rbs +6 -6
  61. data/sig/generated/ruboty/ai_agent/token_usage.rbs +4 -0
  62. data/sig/generated/ruboty/ai_agent/user.rbs +4 -0
  63. data/sig/generated/ruboty/ai_agent/user_prompt_command_definitions.rbs +12 -0
  64. data/sig/generated/ruboty/handlers/ai_agent.rbs +12 -12
  65. data/sig/generated-by-scripts/concerns.rbs +5 -0
  66. data/sig/generated-by-scripts/memorized_ivars.rbs +9 -0
  67. 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 *keys: keynable
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: keynable
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: keynable
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: keynable
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: keynable
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[keynable]
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 = Symbol | String | Integer
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 = {}, &block)
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
- &block
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:, model:)
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:, headers:, url:) # steep:ignore UnexpectedKeywordArgument
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::keynable]
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::keynable
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::keynable
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::keynable
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::keynable
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[Database::keynable, untyped]
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.to_h)
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
- type = hash[:record_type]
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[Database::keynable, untyped]
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:,
@@ -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().reject { |(_key, cache)| cache.expired? }
60
+ super.reject { |(_key, cache)| cache.expired? }
61
61
  end
62
62
 
63
63
  # @rbs override
64
64
  def fetch(key)
65
- cache = super(key)
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 = {}, &block)
30
- mcp_client.call_tool(name, arguments, &block)
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ruboty
4
4
  module AiAgent
5
- VERSION = '0.1.0'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
@@ -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 system prompt "(?<prompt>.+?)"(?: in (?<scope>user|global) scope)?/, name: 'set_system_prompt', description: 'Set system prompt')
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+"(?<prompt>.+)"}, name: 'add_ai_command',
31
- description: 'Add a new AI command')
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
@@ -18,6 +18,7 @@ gems:
18
18
  - name: net-http
19
19
  - name: json
20
20
  - name: optparse
21
+ - name: shellwords
21
22
  # # If you want to avoid installing rbs files for gems, you can specify them here.
22
23
  # - name: GEM_NAME
23
24
  # ignore: true
@@ -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
- 'public gem pushes.'
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