claude_swarm 1.0.1 → 1.0.2

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 (267) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/release.md +1 -1
  3. data/.claude/hooks/lint-code-files.rb +65 -0
  4. data/.rubocop.yml +22 -2
  5. data/CHANGELOG.md +14 -1
  6. data/CLAUDE.md +1 -1
  7. data/CONTRIBUTING.md +69 -0
  8. data/README.md +27 -2
  9. data/Rakefile +71 -3
  10. data/analyze_coverage.rb +94 -0
  11. data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
  12. data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
  13. data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
  14. data/docs/v2/README.md +308 -0
  15. data/docs/v2/guides/claude-code-agents.md +262 -0
  16. data/docs/v2/guides/complete-tutorial.md +3088 -0
  17. data/docs/v2/guides/getting-started.md +1456 -0
  18. data/docs/v2/guides/memory-adapters.md +998 -0
  19. data/docs/v2/guides/plugins.md +816 -0
  20. data/docs/v2/guides/quick-start-cli.md +1745 -0
  21. data/docs/v2/guides/rails-integration.md +1902 -0
  22. data/docs/v2/guides/swarm-memory.md +599 -0
  23. data/docs/v2/reference/cli.md +729 -0
  24. data/docs/v2/reference/ruby-dsl.md +2154 -0
  25. data/docs/v2/reference/yaml.md +1835 -0
  26. data/docs-team-swarm.yml +2222 -0
  27. data/examples/learning-assistant/assistant.md +7 -0
  28. data/examples/learning-assistant/example-memories/concept-example.md +90 -0
  29. data/examples/learning-assistant/example-memories/experience-example.md +66 -0
  30. data/examples/learning-assistant/example-memories/fact-example.md +76 -0
  31. data/examples/learning-assistant/example-memories/memory-index.md +78 -0
  32. data/examples/learning-assistant/example-memories/skill-example.md +168 -0
  33. data/examples/learning-assistant/learning_assistant.rb +34 -0
  34. data/examples/learning-assistant/learning_assistant.yml +20 -0
  35. data/examples/v2/dsl/01_basic.rb +44 -0
  36. data/examples/v2/dsl/02_core_parameters.rb +59 -0
  37. data/examples/v2/dsl/03_capabilities.rb +71 -0
  38. data/examples/v2/dsl/04_llm_parameters.rb +56 -0
  39. data/examples/v2/dsl/05_advanced_flags.rb +73 -0
  40. data/examples/v2/dsl/06_permissions.rb +80 -0
  41. data/examples/v2/dsl/07_mcp_server.rb +62 -0
  42. data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
  43. data/examples/v2/dsl/09_agent_hooks.rb +67 -0
  44. data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
  45. data/examples/v2/dsl/11_delegation.rb +60 -0
  46. data/examples/v2/dsl/12_complete_integration.rb +137 -0
  47. data/examples/v2/file_tools_swarm.yml +102 -0
  48. data/examples/v2/hooks/01_basic_hooks.rb +133 -0
  49. data/examples/v2/hooks/02_usage_tracking.rb +201 -0
  50. data/examples/v2/hooks/03_production_monitoring.rb +429 -0
  51. data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
  52. data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
  53. data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
  54. data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
  55. data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
  56. data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
  57. data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
  58. data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
  59. data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
  60. data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
  61. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
  62. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
  63. data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
  64. data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
  65. data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
  66. data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
  67. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
  68. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
  69. data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
  70. data/examples/v2/hooks/swarm_summary.sh +44 -0
  71. data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
  72. data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
  73. data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
  74. data/examples/v2/hooks/validate_bash.rb +59 -0
  75. data/examples/v2/multi_directory_permissions.yml +221 -0
  76. data/examples/v2/node_context_demo.rb +127 -0
  77. data/examples/v2/node_workflow.rb +173 -0
  78. data/examples/v2/path_resolution_demo.rb +216 -0
  79. data/examples/v2/simple-swarm-v2.rb +90 -0
  80. data/examples/v2/simple-swarm-v2.yml +62 -0
  81. data/examples/v2/swarm.yml +71 -0
  82. data/examples/v2/swarm_with_hooks.yml +61 -0
  83. data/examples/v2/swarm_with_hooks_simple.yml +25 -0
  84. data/examples/v2/think_tool_demo.rb +62 -0
  85. data/exe/swarm +6 -0
  86. data/lib/claude_swarm/claude_mcp_server.rb +0 -6
  87. data/lib/claude_swarm/cli.rb +10 -3
  88. data/lib/claude_swarm/commands/ps.rb +19 -20
  89. data/lib/claude_swarm/commands/show.rb +1 -1
  90. data/lib/claude_swarm/configuration.rb +10 -12
  91. data/lib/claude_swarm/mcp_generator.rb +10 -1
  92. data/lib/claude_swarm/orchestrator.rb +73 -49
  93. data/lib/claude_swarm/system_utils.rb +37 -11
  94. data/lib/claude_swarm/version.rb +1 -1
  95. data/lib/claude_swarm/worktree_manager.rb +1 -0
  96. data/lib/claude_swarm/yaml_loader.rb +22 -0
  97. data/lib/claude_swarm.rb +6 -2
  98. data/lib/swarm_cli/cli.rb +201 -0
  99. data/lib/swarm_cli/command_registry.rb +61 -0
  100. data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
  101. data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
  102. data/lib/swarm_cli/commands/migrate.rb +55 -0
  103. data/lib/swarm_cli/commands/run.rb +173 -0
  104. data/lib/swarm_cli/config_loader.rb +97 -0
  105. data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
  106. data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
  107. data/lib/swarm_cli/interactive_repl.rb +918 -0
  108. data/lib/swarm_cli/mcp_serve_options.rb +44 -0
  109. data/lib/swarm_cli/mcp_tools_options.rb +59 -0
  110. data/lib/swarm_cli/migrate_options.rb +54 -0
  111. data/lib/swarm_cli/migrator.rb +132 -0
  112. data/lib/swarm_cli/options.rb +151 -0
  113. data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
  114. data/lib/swarm_cli/ui/components/content_block.rb +120 -0
  115. data/lib/swarm_cli/ui/components/divider.rb +57 -0
  116. data/lib/swarm_cli/ui/components/panel.rb +62 -0
  117. data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
  118. data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
  119. data/lib/swarm_cli/ui/formatters/number.rb +58 -0
  120. data/lib/swarm_cli/ui/formatters/text.rb +77 -0
  121. data/lib/swarm_cli/ui/formatters/time.rb +73 -0
  122. data/lib/swarm_cli/ui/icons.rb +59 -0
  123. data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
  124. data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
  125. data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
  126. data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
  127. data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
  128. data/lib/swarm_cli/version.rb +5 -0
  129. data/lib/swarm_cli.rb +44 -0
  130. data/lib/swarm_memory/adapters/base.rb +141 -0
  131. data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
  132. data/lib/swarm_memory/chat_extension.rb +34 -0
  133. data/lib/swarm_memory/cli/commands.rb +306 -0
  134. data/lib/swarm_memory/core/entry.rb +37 -0
  135. data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
  136. data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
  137. data/lib/swarm_memory/core/path_normalizer.rb +75 -0
  138. data/lib/swarm_memory/core/semantic_index.rb +244 -0
  139. data/lib/swarm_memory/core/storage.rb +288 -0
  140. data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
  141. data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
  142. data/lib/swarm_memory/dsl/memory_config.rb +113 -0
  143. data/lib/swarm_memory/embeddings/embedder.rb +36 -0
  144. data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
  145. data/lib/swarm_memory/errors.rb +21 -0
  146. data/lib/swarm_memory/integration/cli_registration.rb +30 -0
  147. data/lib/swarm_memory/integration/configuration.rb +43 -0
  148. data/lib/swarm_memory/integration/registration.rb +31 -0
  149. data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
  150. data/lib/swarm_memory/optimization/analyzer.rb +244 -0
  151. data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
  152. data/lib/swarm_memory/prompts/memory.md.erb +109 -0
  153. data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
  154. data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
  155. data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
  156. data/lib/swarm_memory/search/semantic_search.rb +112 -0
  157. data/lib/swarm_memory/search/text_search.rb +42 -0
  158. data/lib/swarm_memory/search/text_similarity.rb +80 -0
  159. data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
  160. data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
  161. data/lib/swarm_memory/tools/load_skill.rb +313 -0
  162. data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
  163. data/lib/swarm_memory/tools/memory_delete.rb +99 -0
  164. data/lib/swarm_memory/tools/memory_edit.rb +185 -0
  165. data/lib/swarm_memory/tools/memory_glob.rb +160 -0
  166. data/lib/swarm_memory/tools/memory_grep.rb +247 -0
  167. data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
  168. data/lib/swarm_memory/tools/memory_read.rb +123 -0
  169. data/lib/swarm_memory/tools/memory_write.rb +231 -0
  170. data/lib/swarm_memory/utils.rb +50 -0
  171. data/lib/swarm_memory/version.rb +5 -0
  172. data/lib/swarm_memory.rb +166 -0
  173. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
  174. data/lib/swarm_sdk/agent/builder.rb +461 -0
  175. data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
  176. data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
  177. data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
  178. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
  179. data/lib/swarm_sdk/agent/chat.rb +1159 -0
  180. data/lib/swarm_sdk/agent/context.rb +112 -0
  181. data/lib/swarm_sdk/agent/context_manager.rb +309 -0
  182. data/lib/swarm_sdk/agent/definition.rb +556 -0
  183. data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
  184. data/lib/swarm_sdk/configuration.rb +296 -0
  185. data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
  186. data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
  187. data/lib/swarm_sdk/context_compactor.rb +340 -0
  188. data/lib/swarm_sdk/hooks/adapter.rb +359 -0
  189. data/lib/swarm_sdk/hooks/context.rb +197 -0
  190. data/lib/swarm_sdk/hooks/definition.rb +80 -0
  191. data/lib/swarm_sdk/hooks/error.rb +29 -0
  192. data/lib/swarm_sdk/hooks/executor.rb +146 -0
  193. data/lib/swarm_sdk/hooks/registry.rb +147 -0
  194. data/lib/swarm_sdk/hooks/result.rb +150 -0
  195. data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
  196. data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
  197. data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
  198. data/lib/swarm_sdk/log_collector.rb +51 -0
  199. data/lib/swarm_sdk/log_stream.rb +69 -0
  200. data/lib/swarm_sdk/markdown_parser.rb +75 -0
  201. data/lib/swarm_sdk/model_aliases.json +5 -0
  202. data/lib/swarm_sdk/models.json +1 -0
  203. data/lib/swarm_sdk/models.rb +120 -0
  204. data/lib/swarm_sdk/node/agent_config.rb +49 -0
  205. data/lib/swarm_sdk/node/builder.rb +439 -0
  206. data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
  207. data/lib/swarm_sdk/node_context.rb +170 -0
  208. data/lib/swarm_sdk/node_orchestrator.rb +384 -0
  209. data/lib/swarm_sdk/permissions/config.rb +239 -0
  210. data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
  211. data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
  212. data/lib/swarm_sdk/permissions/validator.rb +173 -0
  213. data/lib/swarm_sdk/permissions_builder.rb +122 -0
  214. data/lib/swarm_sdk/plugin.rb +147 -0
  215. data/lib/swarm_sdk/plugin_registry.rb +101 -0
  216. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
  217. data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
  218. data/lib/swarm_sdk/result.rb +97 -0
  219. data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
  220. data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
  221. data/lib/swarm_sdk/swarm/builder.rb +586 -0
  222. data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
  223. data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
  224. data/lib/swarm_sdk/swarm.rb +982 -0
  225. data/lib/swarm_sdk/tools/bash.rb +274 -0
  226. data/lib/swarm_sdk/tools/clock.rb +44 -0
  227. data/lib/swarm_sdk/tools/delegate.rb +164 -0
  228. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
  229. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
  230. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
  231. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
  232. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
  233. data/lib/swarm_sdk/tools/edit.rb +150 -0
  234. data/lib/swarm_sdk/tools/glob.rb +158 -0
  235. data/lib/swarm_sdk/tools/grep.rb +228 -0
  236. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
  237. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
  238. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
  239. data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
  240. data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
  241. data/lib/swarm_sdk/tools/read.rb +251 -0
  242. data/lib/swarm_sdk/tools/registry.rb +93 -0
  243. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
  244. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
  245. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
  246. data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
  247. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
  248. data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
  249. data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
  250. data/lib/swarm_sdk/tools/think.rb +95 -0
  251. data/lib/swarm_sdk/tools/todo_write.rb +216 -0
  252. data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
  253. data/lib/swarm_sdk/tools/write.rb +117 -0
  254. data/lib/swarm_sdk/utils.rb +50 -0
  255. data/lib/swarm_sdk/version.rb +5 -0
  256. data/lib/swarm_sdk.rb +157 -0
  257. data/llm.v2.txt +13407 -0
  258. data/rubocop/cop/security/no_reflection_methods.rb +47 -0
  259. data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
  260. data/swarm_cli.gemspec +57 -0
  261. data/swarm_memory.gemspec +28 -0
  262. data/swarm_sdk.gemspec +41 -0
  263. data/team.yml +1 -1
  264. data/team_full.yml +1875 -0
  265. data/{team_v2.yml → team_sdk.yml} +121 -52
  266. metadata +247 -4
  267. data/EXAMPLES.md +0 -164
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Bash tool for executing shell commands
6
+ #
7
+ # Executes commands in a persistent shell session with timeout support.
8
+ # Provides comprehensive guidance on proper usage patterns.
9
+ class Bash < RubyLLM::Tool
10
+ def initialize(directory:)
11
+ super()
12
+ @directory = File.expand_path(directory)
13
+ end
14
+
15
+ def name
16
+ "Bash"
17
+ end
18
+
19
+ description <<~DESC
20
+ Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
21
+
22
+ IMPORTANT: This tool is for terminal operations like git, npm, docker, etc. DO NOT use it for file operations (reading, writing, editing, searching, finding files) - use the specialized tools for this instead.
23
+
24
+ Before executing the command, please follow these steps:
25
+
26
+ 1. Directory Verification:
27
+ - If the command will create new directories or files, first use `ls` to verify the parent directory exists and is the correct location
28
+ - For example, before running "mkdir foo/bar", first use `ls foo` to check that "foo" exists and is the intended parent directory
29
+
30
+ 2. Command Execution:
31
+ - Always quote file paths that contain spaces with double quotes (e.g., cd "path with spaces/file.txt")
32
+ - Examples of proper quoting:
33
+ - cd "/Users/name/My Documents" (correct)
34
+ - cd /Users/name/My Documents (incorrect - will fail)
35
+ - python "/path/with spaces/script.py" (correct)
36
+ - python /path/with spaces/script.py (incorrect - will fail)
37
+ - After ensuring proper quoting, execute the command.
38
+ - Capture the output of the command.
39
+
40
+ Usage notes:
41
+ - The command argument is required.
42
+ - You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 120000ms (2 minutes).
43
+ - It is very helpful if you write a clear, concise description of what this command does in 5-10 words.
44
+ - If the output exceeds 30000 characters, output will be truncated before being returned to you.
45
+ - Avoid using Bash with the `find`, `grep`, `cat`, `head`, `tail`, `sed`, `awk`, or `echo` commands, unless explicitly instructed or when these commands are truly necessary for the task. Instead, always prefer using the dedicated tools for these commands:
46
+ - File search: Use Glob (NOT find or ls)
47
+ - Content search: Use Grep (NOT grep or rg)
48
+ - Read files: Use Read (NOT cat/head/tail)
49
+ - Edit files: Use Edit (NOT sed/awk)
50
+ - Write files: Use Write (NOT echo >/cat <<EOF)
51
+ - Communication: Output text directly (NOT echo/printf)
52
+ - When issuing multiple commands:
53
+ - If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message. For example, if you need to run "git status" and "git diff", send a single message with two Bash tool calls in parallel.
54
+ - If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them together (e.g., `git add . && git commit -m "message" && git push`). For instance, if one operation must complete before another starts (like mkdir before cp, Write before Bash for git operations, or git add before git commit), run these operations sequentially instead.
55
+ - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail
56
+ - DO NOT use newlines to separate commands (newlines are ok in quoted strings)
57
+ - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of `cd`. You may use `cd` if the User explicitly requests it.
58
+ <good-example>
59
+ pytest /foo/bar/tests
60
+ </good-example>
61
+ <bad-example>
62
+ cd /foo/bar && pytest tests
63
+ </bad-example>
64
+ DESC
65
+
66
+ param :command,
67
+ type: "string",
68
+ desc: "The command to execute",
69
+ required: true
70
+
71
+ param :description,
72
+ type: "string",
73
+ desc: "Clear, concise description of what this command does in 5-10 words, in active voice. Examples:\nInput: ls\nOutput: List files in current directory\n\nInput: git status\nOutput: Show working tree status\n\nInput: npm install\nOutput: Install package dependencies\n\nInput: mkdir foo\nOutput: Create directory 'foo'",
74
+ required: false
75
+
76
+ param :timeout,
77
+ type: "number",
78
+ desc: "Optional timeout in milliseconds (max 600000)",
79
+ required: false
80
+
81
+ DEFAULT_TIMEOUT_MS = 120_000 # 2 minutes
82
+ MAX_TIMEOUT_MS = 600_000 # 10 minutes
83
+ MAX_OUTPUT_LENGTH = 30_000 # characters
84
+
85
+ # Commands that are ALWAYS blocked for safety reasons
86
+ # These cannot be overridden by permissions configuration
87
+ ALWAYS_BLOCKED_COMMANDS = [
88
+ %r{^rm\s+-rf\s+/$}, # rm -rf / - delete root filesystem
89
+ ].freeze
90
+
91
+ def execute(command:, description: nil, timeout: nil)
92
+ # Validate inputs
93
+ return validation_error("command is required") if command.nil? || command.empty?
94
+
95
+ # Check against always-blocked commands
96
+ blocked_pattern = ALWAYS_BLOCKED_COMMANDS.find { |pattern| pattern.match?(command) }
97
+ if blocked_pattern
98
+ return blocked_command_error(command, blocked_pattern)
99
+ end
100
+
101
+ # Validate and set timeout
102
+ timeout_ms = timeout || DEFAULT_TIMEOUT_MS
103
+ timeout_ms = [timeout_ms, MAX_TIMEOUT_MS].min
104
+ timeout_seconds = timeout_ms / 1000.0
105
+
106
+ # Execute command with timeout
107
+ stdout = +""
108
+ stderr = +""
109
+ exit_status = nil
110
+
111
+ begin
112
+ require "open3"
113
+ require "timeout"
114
+
115
+ Timeout.timeout(timeout_seconds) do
116
+ # CRITICAL: Change to agent's directory for subprocess
117
+ # This is SAFE because Open3.popen3 creates a subprocess
118
+ # The subprocess inherits the directory, but the parent fiber is unaffected
119
+ Dir.chdir(@directory) do
120
+ Open3.popen3(command) do |stdin, out, err, wait_thr|
121
+ stdin.close # Close stdin since we don't send input
122
+
123
+ # Read stdout and stderr
124
+ stdout = out.read || ""
125
+ stderr = err.read || ""
126
+ exit_status = wait_thr.value.exitstatus
127
+ end
128
+ end
129
+ end
130
+ rescue Timeout::Error
131
+ return format_timeout_error(command, timeout_seconds)
132
+ rescue Errno::ENOENT => e
133
+ return error("Command not found or executable not in PATH: #{e.message}")
134
+ rescue Errno::EACCES
135
+ return error("Permission denied: Cannot execute command '#{command}'")
136
+ rescue StandardError => e
137
+ return error("Failed to execute command: #{e.class.name} - #{e.message}")
138
+ end
139
+
140
+ # Build output
141
+ output = format_command_output(command, description, stdout, stderr, exit_status)
142
+
143
+ # Truncate if too long
144
+ if output.length > MAX_OUTPUT_LENGTH
145
+ truncated = output[0...MAX_OUTPUT_LENGTH]
146
+ truncated += "\n\n<system-reminder>Output truncated at #{MAX_OUTPUT_LENGTH} characters. The full output was #{output.length} characters.</system-reminder>"
147
+ output = truncated
148
+ end
149
+
150
+ # Add usage reminders for certain patterns
151
+ output = add_usage_reminders(output, command)
152
+
153
+ output
154
+ rescue StandardError => e
155
+ error("Unexpected error executing command: #{e.class.name} - #{e.message}")
156
+ end
157
+
158
+ private
159
+
160
+ def validation_error(message)
161
+ "<tool_use_error>InputValidationError: #{message}</tool_use_error>"
162
+ end
163
+
164
+ def error(message)
165
+ "Error: #{message}"
166
+ end
167
+
168
+ def blocked_command_error(command, pattern)
169
+ <<~ERROR
170
+ Error: Command blocked for safety reasons.
171
+ Command: #{command}
172
+ Pattern: #{pattern.source}
173
+
174
+ <system-reminder>
175
+ SECURITY BLOCK: This command is permanently blocked for safety reasons and cannot be executed.
176
+
177
+ This is a built-in safety feature of the Bash tool that cannot be overridden by any configuration.
178
+ The command matches a pattern that could cause catastrophic system damage.
179
+
180
+ DO NOT attempt to:
181
+ - Modify the command slightly to bypass this check
182
+ - Ask the user to allow this command
183
+ - Work around this restriction in any way
184
+
185
+ If you need to perform a similar operation safely, consider:
186
+ - Using a more specific path instead of system-wide operations
187
+ - Using dedicated tools for file operations
188
+ - Asking the user for guidance on a safer approach
189
+
190
+ This is an UNRECOVERABLE error. You must inform the user that this command cannot be executed for safety reasons.
191
+ </system-reminder>
192
+ ERROR
193
+ end
194
+
195
+ def format_timeout_error(command, timeout_seconds)
196
+ <<~ERROR
197
+ Error: Command timed out after #{timeout_seconds} seconds.
198
+ Command: #{command}
199
+
200
+ <system-reminder>The command exceeded the timeout limit. Consider:
201
+ 1. Breaking the command into smaller steps
202
+ 2. Increasing the timeout parameter
203
+ 3. Running long-running commands in the background if supported
204
+ </system-reminder>
205
+ ERROR
206
+ end
207
+
208
+ def format_command_output(command, description, stdout, stderr, exit_status)
209
+ parts = []
210
+
211
+ # Add description if provided
212
+ parts << "Running: #{description}" if description
213
+
214
+ # Add command
215
+ parts << "$ #{command}"
216
+ parts << ""
217
+
218
+ # Add exit status
219
+ parts << "Exit code: #{exit_status}"
220
+
221
+ # Add stdout if present
222
+ if stdout && !stdout.empty?
223
+ parts << ""
224
+ parts << "STDOUT:"
225
+ parts << stdout.chomp
226
+ end
227
+
228
+ # Add stderr if present
229
+ if stderr && !stderr.empty?
230
+ parts << ""
231
+ parts << "STDERR:"
232
+ parts << stderr.chomp
233
+ end
234
+
235
+ # Add warning for non-zero exit
236
+ if exit_status != 0
237
+ parts << ""
238
+ parts << "<system-reminder>Command exited with non-zero status (#{exit_status}). Check STDERR for error details.</system-reminder>"
239
+ end
240
+
241
+ parts.join("\n")
242
+ end
243
+
244
+ def add_usage_reminders(output, command)
245
+ reminders = []
246
+
247
+ # Detect file operation commands that should use dedicated tools
248
+ if command.match?(/\b(cat|head|tail|less|more)\s+/)
249
+ reminders << "You used a command to read a file. Consider using the Read tool instead for better formatting and error handling."
250
+ end
251
+
252
+ if command.match?(/\b(grep|rg|ag)\s+/)
253
+ reminders << "You used grep/ripgrep to search files. Consider using the Grep tool instead for structured results."
254
+ end
255
+
256
+ if command.match?(/\b(find|locate)\s+/)
257
+ reminders << "You used find to locate files. Consider using the Glob tool instead for pattern-based file matching."
258
+ end
259
+
260
+ if command.match?(/\b(sed|awk)\s+/) && !command.include?("|")
261
+ reminders << "You used sed/awk for file editing. Consider using the Edit tool instead for safer, tracked file modifications."
262
+ end
263
+
264
+ if command.match?(/\becho\s+.*>\s*/) || command.match?(/\bcat\s*<</)
265
+ reminders << "You used echo/cat with redirection to write a file. Consider using the Write tool instead for proper file creation."
266
+ end
267
+
268
+ return output if reminders.empty?
269
+
270
+ output + "\n\n<system-reminder>\n#{reminders.join("\n\n")}\n</system-reminder>"
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Clock tool provides current date and time information
6
+ #
7
+ # Returns current temporal information in a consistent format.
8
+ # Agents use this when they need to know what day/time it is.
9
+ class Clock < RubyLLM::Tool
10
+ description <<~DESC
11
+ Get current date and time.
12
+
13
+ Returns:
14
+ - Current date (YYYY-MM-DD format)
15
+ - Current time (HH:MM:SS format)
16
+ - Day of week (Monday, Tuesday, etc.)
17
+ - ISO 8601 timestamp (full datetime)
18
+
19
+ Use this when you need to know what day it is, what time it is,
20
+ or to store temporal information (e.g., "As of 2025-10-20...").
21
+
22
+ No parameters needed - just call Clock() to get complete temporal information.
23
+ DESC
24
+
25
+ # No parameters needed
26
+
27
+ # Override name to return simple "Clock"
28
+ def name
29
+ "Clock"
30
+ end
31
+
32
+ def execute
33
+ now = Time.now
34
+
35
+ <<~RESULT.chomp
36
+ Current date: #{now.strftime("%Y-%m-%d")}
37
+ Current time: #{now.strftime("%H:%M:%S")}
38
+ Day of week: #{now.strftime("%A")}
39
+ ISO 8601: #{now.iso8601}
40
+ RESULT
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ # Delegate tool for delegating tasks to other agents in the swarm
6
+ #
7
+ # Creates agent-specific delegation tools (e.g., DelegateTaskToBackend)
8
+ # that allow one agent to delegate work to another agent.
9
+ # Supports pre/post delegation hooks for customization.
10
+ class Delegate < RubyLLM::Tool
11
+ attr_reader :delegate_name, :delegate_target, :tool_name
12
+
13
+ # Initialize a delegation tool
14
+ #
15
+ # @param delegate_name [String] Name of the delegate agent (e.g., "backend")
16
+ # @param delegate_description [String] Description of the delegate agent
17
+ # @param delegate_chat [AgentChat] The chat instance for the delegate agent
18
+ # @param agent_name [Symbol, String] Name of the agent using this tool
19
+ # @param swarm [Swarm] The swarm instance
20
+ # @param hook_registry [Hooks::Registry] Registry for callbacks
21
+ # @param delegating_chat [Agent::Chat, nil] The chat instance of the agent doing the delegating (for accessing hooks)
22
+ def initialize(
23
+ delegate_name:,
24
+ delegate_description:,
25
+ delegate_chat:,
26
+ agent_name:,
27
+ swarm:,
28
+ hook_registry:,
29
+ delegating_chat: nil
30
+ )
31
+ super()
32
+
33
+ @delegate_name = delegate_name
34
+ @delegate_description = delegate_description
35
+ @delegate_chat = delegate_chat
36
+ @agent_name = agent_name
37
+ @swarm = swarm
38
+ @hook_registry = hook_registry
39
+ @delegating_chat = delegating_chat
40
+
41
+ # Generate tool name in the expected format: DelegateTaskTo[AgentName]
42
+ @tool_name = "DelegateTaskTo#{delegate_name.to_s.capitalize}"
43
+ @delegate_target = delegate_name.to_s
44
+ end
45
+
46
+ # Build description dynamically based on delegate
47
+ description do
48
+ "Delegate tasks to #{@delegate_name}. #{@delegate_description}"
49
+ end
50
+
51
+ param :task,
52
+ type: "string",
53
+ desc: "Task description for the agent",
54
+ required: true
55
+
56
+ # Override name to return custom delegation tool name
57
+ def name
58
+ @tool_name
59
+ end
60
+
61
+ # Execute delegation with pre/post hooks
62
+ #
63
+ # @param task [String] Task to delegate
64
+ # @return [String] Result from delegate agent or error message
65
+ def execute(task:)
66
+ # Get agent-specific hooks from the delegating chat instance
67
+ agent_hooks = if @delegating_chat&.respond_to?(:hook_agent_hooks)
68
+ @delegating_chat.hook_agent_hooks || {}
69
+ else
70
+ {}
71
+ end
72
+
73
+ # Trigger pre_delegation callback
74
+ context = Hooks::Context.new(
75
+ event: :pre_delegation,
76
+ agent_name: @agent_name,
77
+ swarm: @swarm,
78
+ delegation_target: @delegate_target,
79
+ metadata: {
80
+ tool_name: @tool_name,
81
+ task: task,
82
+ timestamp: Time.now.utc.iso8601,
83
+ },
84
+ )
85
+
86
+ executor = Hooks::Executor.new(@hook_registry, logger: RubyLLM.logger)
87
+ pre_agent_hooks = agent_hooks[:pre_delegation] || []
88
+ result = executor.execute_safe(event: :pre_delegation, context: context, callbacks: pre_agent_hooks)
89
+
90
+ # Check if callback halted or replaced the delegation
91
+ if result.halt?
92
+ return result.value || "Delegation halted by callback"
93
+ elsif result.replace?
94
+ return result.value
95
+ end
96
+
97
+ # Proceed with delegation
98
+ response = @delegate_chat.ask(task)
99
+ delegation_result = response.content
100
+
101
+ # Trigger post_delegation callback
102
+ post_context = Hooks::Context.new(
103
+ event: :post_delegation,
104
+ agent_name: @agent_name,
105
+ swarm: @swarm,
106
+ delegation_target: @delegate_target,
107
+ delegation_result: delegation_result,
108
+ metadata: {
109
+ tool_name: @tool_name,
110
+ task: task,
111
+ result: delegation_result,
112
+ timestamp: Time.now.utc.iso8601,
113
+ },
114
+ )
115
+
116
+ post_agent_hooks = agent_hooks[:post_delegation] || []
117
+ post_result = executor.execute_safe(event: :post_delegation, context: post_context, callbacks: post_agent_hooks)
118
+
119
+ # Return modified result if callback replaces it
120
+ if post_result.replace?
121
+ post_result.value
122
+ else
123
+ delegation_result
124
+ end
125
+ rescue Faraday::TimeoutError, Net::ReadTimeout => e
126
+ # Log timeout error as JSON event
127
+ LogStream.emit(
128
+ type: "delegation_error",
129
+ agent: @agent_name,
130
+ delegate_to: @tool_name,
131
+ error_class: e.class.name,
132
+ error_message: "Request timed out",
133
+ backtrace: e.backtrace&.first(5) || [],
134
+ )
135
+ "Error: Request to #{@tool_name} timed out. The agent may be overloaded or the LLM service is not responding. Please try again or simplify the task."
136
+ rescue Faraday::Error => e
137
+ # Log network error as JSON event
138
+ LogStream.emit(
139
+ type: "delegation_error",
140
+ agent: @agent_name,
141
+ delegate_to: @tool_name,
142
+ error_class: e.class.name,
143
+ error_message: e.message,
144
+ backtrace: e.backtrace&.first(5) || [],
145
+ )
146
+ "Error: Network error communicating with #{@tool_name}: #{e.class.name}. Please check connectivity and try again."
147
+ rescue StandardError => e
148
+ # Log unexpected error as JSON event
149
+ backtrace_array = e.backtrace&.first(5) || []
150
+ LogStream.emit(
151
+ type: "delegation_error",
152
+ agent: @agent_name,
153
+ delegate_to: @tool_name,
154
+ error_class: e.class.name,
155
+ error_message: e.message,
156
+ backtrace: backtrace_array,
157
+ )
158
+ # Return error string for LLM
159
+ backtrace_str = backtrace_array.join("\n ")
160
+ "Error: #{@tool_name} encountered an error: #{e.class.name}: #{e.message}\nBacktrace:\n #{backtrace_str}"
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ module DocumentConverters
6
+ # Base class for document converters
7
+ # Provides common interface and utility methods for converting various document formats
8
+ class BaseConverter
9
+ class << self
10
+ # The gem name required for this converter
11
+ # @return [String]
12
+ def gem_name
13
+ raise NotImplementedError, "#{name} must implement .gem_name"
14
+ end
15
+
16
+ # Human-readable format name
17
+ # @return [String]
18
+ def format_name
19
+ raise NotImplementedError, "#{name} must implement .format_name"
20
+ end
21
+
22
+ # File extensions this converter handles
23
+ # @return [Array<String>]
24
+ def extensions
25
+ raise NotImplementedError, "#{name} must implement .extensions"
26
+ end
27
+
28
+ # Check if the required gem is available
29
+ # @return [Boolean]
30
+ def available?
31
+ gem_available?(gem_name)
32
+ end
33
+
34
+ # Check if a gem is installed
35
+ # @param gem_name [String] Name of the gem to check
36
+ # @return [Boolean]
37
+ def gem_available?(gem_name)
38
+ Gem::Specification.find_by_name(gem_name)
39
+ true
40
+ rescue Gem::LoadError
41
+ false
42
+ end
43
+ end
44
+
45
+ # Convert a document file to text/content
46
+ # @param file_path [String] Path to the file
47
+ # @return [String, RubyLLM::Content] Converted content or error message
48
+ def convert(file_path)
49
+ raise NotImplementedError, "#{self.class.name} must implement #convert"
50
+ end
51
+
52
+ protected
53
+
54
+ # Return a system reminder about missing gem
55
+ # @param format [String] Format name (e.g., "PDF")
56
+ # @param gem_name [String] Required gem name
57
+ # @return [String]
58
+ def unsupported_format_reminder(format, gem_name)
59
+ <<~REMINDER
60
+ <system-reminder>
61
+ This file is a #{format} document, but the required gem is not installed.
62
+
63
+ To enable #{format} file reading, please install the gem:
64
+ gem install #{gem_name}
65
+
66
+ Or add to your Gemfile:
67
+ gem "#{gem_name}"
68
+
69
+ Don't install the gem yourself. Ask the user if they would like you to install this gem.
70
+ </system-reminder>
71
+ REMINDER
72
+ end
73
+
74
+ # Return an error message
75
+ # @param message [String] Error message
76
+ # @return [String]
77
+ def error(message)
78
+ "Error: #{message}"
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module Tools
5
+ module DocumentConverters
6
+ # Converts DOCX documents to text with image extraction
7
+ class DocxConverter < BaseConverter
8
+ class << self
9
+ def gem_name
10
+ "docx"
11
+ end
12
+
13
+ def format_name
14
+ "DOCX"
15
+ end
16
+
17
+ def extensions
18
+ [".docx", ".doc"]
19
+ end
20
+ end
21
+
22
+ # Convert a DOCX document to text/content
23
+ # @param file_path [String] Path to the DOCX file
24
+ # @return [String, RubyLLM::Content] Converted content or error message
25
+ def convert(file_path)
26
+ unless self.class.available?
27
+ return unsupported_format_reminder(self.class.format_name, self.class.gem_name)
28
+ end
29
+
30
+ # Check for legacy DOC format
31
+ if File.extname(file_path).downcase == ".doc"
32
+ return error("DOC format is not supported. Please convert to DOCX first.")
33
+ end
34
+
35
+ begin
36
+ require "docx"
37
+ require "tmpdir"
38
+
39
+ doc = Docx::Document.open(file_path)
40
+
41
+ # Extract images from the DOCX
42
+ image_paths = ImageExtractors::DocxImageExtractor.extract_images(doc, file_path)
43
+
44
+ output = []
45
+ output << "Document: #{File.basename(file_path)}"
46
+ output << "=" * 60
47
+ output << ""
48
+
49
+ # Extract paragraphs
50
+ paragraphs = doc.paragraphs.map(&:text).reject(&:empty?)
51
+
52
+ # Check for empty document
53
+ if paragraphs.empty? && doc.tables.empty?
54
+ output << "(Document is empty - no paragraphs or tables)"
55
+ else
56
+ output += paragraphs
57
+
58
+ # Extract tables with enhanced formatting
59
+ if doc.tables.any?
60
+ output << ""
61
+ output << "Tables:"
62
+ output << "-" * 60
63
+
64
+ doc.tables.each_with_index do |table, idx|
65
+ output << ""
66
+ output << "Table #{idx + 1} (#{table.row_count} rows × #{table.column_count} columns):"
67
+
68
+ table.rows.each do |row|
69
+ output << row.cells.map(&:text).join(" | ")
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ text_content = output.join("\n")
76
+
77
+ # If there are images, return Content with attachments
78
+ if image_paths.any?
79
+ content = RubyLLM::Content.new(text_content)
80
+ image_paths.each do |image_path|
81
+ content.add_attachment(image_path)
82
+ end
83
+ content
84
+ else
85
+ # No images, return just text
86
+ text_content
87
+ end
88
+ rescue Zip::Error => e
89
+ error("Invalid or corrupted DOCX file: #{e.message}")
90
+ rescue Errno::ENOENT => e
91
+ error("File not found or missing document.xml: #{e.message}")
92
+ rescue StandardError => e
93
+ error("Failed to parse DOCX file: #{e.message}")
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end