claude_swarm 1.0.0 → 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.
- checksums.yaml +4 -4
- data/.claude/commands/release.md +1 -1
- data/.claude/hooks/lint-code-files.rb +65 -0
- data/.rubocop.yml +22 -2
- data/CHANGELOG.md +21 -1
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +69 -0
- data/README.md +27 -2
- data/Rakefile +71 -3
- data/analyze_coverage.rb +94 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +43 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +379 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +362 -0
- data/docs/v2/README.md +308 -0
- data/docs/v2/guides/claude-code-agents.md +262 -0
- data/docs/v2/guides/complete-tutorial.md +3088 -0
- data/docs/v2/guides/getting-started.md +1456 -0
- data/docs/v2/guides/memory-adapters.md +998 -0
- data/docs/v2/guides/plugins.md +816 -0
- data/docs/v2/guides/quick-start-cli.md +1745 -0
- data/docs/v2/guides/rails-integration.md +1902 -0
- data/docs/v2/guides/swarm-memory.md +599 -0
- data/docs/v2/reference/cli.md +729 -0
- data/docs/v2/reference/ruby-dsl.md +2154 -0
- data/docs/v2/reference/yaml.md +1835 -0
- data/docs-team-swarm.yml +2222 -0
- data/examples/learning-assistant/assistant.md +7 -0
- data/examples/learning-assistant/example-memories/concept-example.md +90 -0
- data/examples/learning-assistant/example-memories/experience-example.md +66 -0
- data/examples/learning-assistant/example-memories/fact-example.md +76 -0
- data/examples/learning-assistant/example-memories/memory-index.md +78 -0
- data/examples/learning-assistant/example-memories/skill-example.md +168 -0
- data/examples/learning-assistant/learning_assistant.rb +34 -0
- data/examples/learning-assistant/learning_assistant.yml +20 -0
- data/examples/v2/dsl/01_basic.rb +44 -0
- data/examples/v2/dsl/02_core_parameters.rb +59 -0
- data/examples/v2/dsl/03_capabilities.rb +71 -0
- data/examples/v2/dsl/04_llm_parameters.rb +56 -0
- data/examples/v2/dsl/05_advanced_flags.rb +73 -0
- data/examples/v2/dsl/06_permissions.rb +80 -0
- data/examples/v2/dsl/07_mcp_server.rb +62 -0
- data/examples/v2/dsl/08_swarm_hooks.rb +53 -0
- data/examples/v2/dsl/09_agent_hooks.rb +67 -0
- data/examples/v2/dsl/10_all_agents_hooks.rb +67 -0
- data/examples/v2/dsl/11_delegation.rb +60 -0
- data/examples/v2/dsl/12_complete_integration.rb +137 -0
- data/examples/v2/file_tools_swarm.yml +102 -0
- data/examples/v2/hooks/01_basic_hooks.rb +133 -0
- data/examples/v2/hooks/02_usage_tracking.rb +201 -0
- data/examples/v2/hooks/03_production_monitoring.rb +429 -0
- data/examples/v2/hooks/agent_stop_exit_0.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_1.yml +21 -0
- data/examples/v2/hooks/agent_stop_exit_2.yml +26 -0
- data/examples/v2/hooks/multiple_hooks_all_pass.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_first_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_second_fails.yml +37 -0
- data/examples/v2/hooks/multiple_hooks_warnings.yml +37 -0
- data/examples/v2/hooks/post_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/post_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/post_tool_use_multi_matcher_exit_2.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_exit_0.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_1.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_exit_2.yml +24 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_0.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_1.yml +26 -0
- data/examples/v2/hooks/pre_tool_use_multi_matcher_exit_2.yml +27 -0
- data/examples/v2/hooks/swarm_summary.sh +44 -0
- data/examples/v2/hooks/user_prompt_exit_0.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_1.yml +21 -0
- data/examples/v2/hooks/user_prompt_exit_2.yml +21 -0
- data/examples/v2/hooks/validate_bash.rb +59 -0
- data/examples/v2/multi_directory_permissions.yml +221 -0
- data/examples/v2/node_context_demo.rb +127 -0
- data/examples/v2/node_workflow.rb +173 -0
- data/examples/v2/path_resolution_demo.rb +216 -0
- data/examples/v2/simple-swarm-v2.rb +90 -0
- data/examples/v2/simple-swarm-v2.yml +62 -0
- data/examples/v2/swarm.yml +71 -0
- data/examples/v2/swarm_with_hooks.yml +61 -0
- data/examples/v2/swarm_with_hooks_simple.yml +25 -0
- data/examples/v2/think_tool_demo.rb +62 -0
- data/exe/swarm +6 -0
- data/lib/claude_swarm/claude_mcp_server.rb +0 -6
- data/lib/claude_swarm/cli.rb +10 -3
- data/lib/claude_swarm/commands/ps.rb +19 -20
- data/lib/claude_swarm/commands/show.rb +1 -1
- data/lib/claude_swarm/configuration.rb +10 -12
- data/lib/claude_swarm/mcp_generator.rb +10 -1
- data/lib/claude_swarm/orchestrator.rb +73 -49
- data/lib/claude_swarm/system_utils.rb +37 -11
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/claude_swarm/worktree_manager.rb +1 -0
- data/lib/claude_swarm/yaml_loader.rb +22 -0
- data/lib/claude_swarm.rb +7 -3
- data/lib/swarm_cli/cli.rb +201 -0
- data/lib/swarm_cli/command_registry.rb +61 -0
- data/lib/swarm_cli/commands/mcp_serve.rb +130 -0
- data/lib/swarm_cli/commands/mcp_tools.rb +148 -0
- data/lib/swarm_cli/commands/migrate.rb +55 -0
- data/lib/swarm_cli/commands/run.rb +173 -0
- data/lib/swarm_cli/config_loader.rb +97 -0
- data/lib/swarm_cli/formatters/human_formatter.rb +711 -0
- data/lib/swarm_cli/formatters/json_formatter.rb +51 -0
- data/lib/swarm_cli/interactive_repl.rb +918 -0
- data/lib/swarm_cli/mcp_serve_options.rb +44 -0
- data/lib/swarm_cli/mcp_tools_options.rb +59 -0
- data/lib/swarm_cli/migrate_options.rb +54 -0
- data/lib/swarm_cli/migrator.rb +132 -0
- data/lib/swarm_cli/options.rb +151 -0
- data/lib/swarm_cli/ui/components/agent_badge.rb +33 -0
- data/lib/swarm_cli/ui/components/content_block.rb +120 -0
- data/lib/swarm_cli/ui/components/divider.rb +57 -0
- data/lib/swarm_cli/ui/components/panel.rb +62 -0
- data/lib/swarm_cli/ui/components/usage_stats.rb +70 -0
- data/lib/swarm_cli/ui/formatters/cost.rb +49 -0
- data/lib/swarm_cli/ui/formatters/number.rb +58 -0
- data/lib/swarm_cli/ui/formatters/text.rb +77 -0
- data/lib/swarm_cli/ui/formatters/time.rb +73 -0
- data/lib/swarm_cli/ui/icons.rb +59 -0
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +188 -0
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +45 -0
- data/lib/swarm_cli/ui/state/depth_tracker.rb +40 -0
- data/lib/swarm_cli/ui/state/spinner_manager.rb +170 -0
- data/lib/swarm_cli/ui/state/usage_tracker.rb +62 -0
- data/lib/swarm_cli/version.rb +5 -0
- data/lib/swarm_cli.rb +44 -0
- data/lib/swarm_memory/adapters/base.rb +141 -0
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +845 -0
- data/lib/swarm_memory/chat_extension.rb +34 -0
- data/lib/swarm_memory/cli/commands.rb +306 -0
- data/lib/swarm_memory/core/entry.rb +37 -0
- data/lib/swarm_memory/core/frontmatter_parser.rb +108 -0
- data/lib/swarm_memory/core/metadata_extractor.rb +68 -0
- data/lib/swarm_memory/core/path_normalizer.rb +75 -0
- data/lib/swarm_memory/core/semantic_index.rb +244 -0
- data/lib/swarm_memory/core/storage.rb +288 -0
- data/lib/swarm_memory/core/storage_read_tracker.rb +63 -0
- data/lib/swarm_memory/dsl/builder_extension.rb +40 -0
- data/lib/swarm_memory/dsl/memory_config.rb +113 -0
- data/lib/swarm_memory/embeddings/embedder.rb +36 -0
- data/lib/swarm_memory/embeddings/informers_embedder.rb +152 -0
- data/lib/swarm_memory/errors.rb +21 -0
- data/lib/swarm_memory/integration/cli_registration.rb +30 -0
- data/lib/swarm_memory/integration/configuration.rb +43 -0
- data/lib/swarm_memory/integration/registration.rb +31 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +531 -0
- data/lib/swarm_memory/optimization/analyzer.rb +244 -0
- data/lib/swarm_memory/optimization/defragmenter.rb +863 -0
- data/lib/swarm_memory/prompts/memory.md.erb +109 -0
- data/lib/swarm_memory/prompts/memory_assistant.md.erb +181 -0
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +281 -0
- data/lib/swarm_memory/prompts/memory_retrieval.md.erb +78 -0
- data/lib/swarm_memory/search/semantic_search.rb +112 -0
- data/lib/swarm_memory/search/text_search.rb +42 -0
- data/lib/swarm_memory/search/text_similarity.rb +80 -0
- data/lib/swarm_memory/skills/meta/deep-learning.md +101 -0
- data/lib/swarm_memory/skills/meta/deep-learning.yml +14 -0
- data/lib/swarm_memory/tools/load_skill.rb +313 -0
- data/lib/swarm_memory/tools/memory_defrag.rb +382 -0
- data/lib/swarm_memory/tools/memory_delete.rb +99 -0
- data/lib/swarm_memory/tools/memory_edit.rb +185 -0
- data/lib/swarm_memory/tools/memory_glob.rb +160 -0
- data/lib/swarm_memory/tools/memory_grep.rb +247 -0
- data/lib/swarm_memory/tools/memory_multi_edit.rb +281 -0
- data/lib/swarm_memory/tools/memory_read.rb +123 -0
- data/lib/swarm_memory/tools/memory_write.rb +231 -0
- data/lib/swarm_memory/utils.rb +50 -0
- data/lib/swarm_memory/version.rb +5 -0
- data/lib/swarm_memory.rb +166 -0
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +127 -0
- data/lib/swarm_sdk/agent/builder.rb +461 -0
- data/lib/swarm_sdk/agent/chat/context_tracker.rb +314 -0
- data/lib/swarm_sdk/agent/chat/hook_integration.rb +372 -0
- data/lib/swarm_sdk/agent/chat/logging_helpers.rb +116 -0
- data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +152 -0
- data/lib/swarm_sdk/agent/chat.rb +1159 -0
- data/lib/swarm_sdk/agent/context.rb +112 -0
- data/lib/swarm_sdk/agent/context_manager.rb +309 -0
- data/lib/swarm_sdk/agent/definition.rb +556 -0
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +205 -0
- data/lib/swarm_sdk/configuration.rb +296 -0
- data/lib/swarm_sdk/context_compactor/metrics.rb +147 -0
- data/lib/swarm_sdk/context_compactor/token_counter.rb +106 -0
- data/lib/swarm_sdk/context_compactor.rb +340 -0
- data/lib/swarm_sdk/hooks/adapter.rb +359 -0
- data/lib/swarm_sdk/hooks/context.rb +197 -0
- data/lib/swarm_sdk/hooks/definition.rb +80 -0
- data/lib/swarm_sdk/hooks/error.rb +29 -0
- data/lib/swarm_sdk/hooks/executor.rb +146 -0
- data/lib/swarm_sdk/hooks/registry.rb +147 -0
- data/lib/swarm_sdk/hooks/result.rb +150 -0
- data/lib/swarm_sdk/hooks/shell_executor.rb +254 -0
- data/lib/swarm_sdk/hooks/tool_call.rb +35 -0
- data/lib/swarm_sdk/hooks/tool_result.rb +62 -0
- data/lib/swarm_sdk/log_collector.rb +51 -0
- data/lib/swarm_sdk/log_stream.rb +69 -0
- data/lib/swarm_sdk/markdown_parser.rb +75 -0
- data/lib/swarm_sdk/model_aliases.json +5 -0
- data/lib/swarm_sdk/models.json +1 -0
- data/lib/swarm_sdk/models.rb +120 -0
- data/lib/swarm_sdk/node/agent_config.rb +49 -0
- data/lib/swarm_sdk/node/builder.rb +439 -0
- data/lib/swarm_sdk/node/transformer_executor.rb +248 -0
- data/lib/swarm_sdk/node_context.rb +170 -0
- data/lib/swarm_sdk/node_orchestrator.rb +384 -0
- data/lib/swarm_sdk/permissions/config.rb +239 -0
- data/lib/swarm_sdk/permissions/error_formatter.rb +121 -0
- data/lib/swarm_sdk/permissions/path_matcher.rb +35 -0
- data/lib/swarm_sdk/permissions/validator.rb +173 -0
- data/lib/swarm_sdk/permissions_builder.rb +122 -0
- data/lib/swarm_sdk/plugin.rb +147 -0
- data/lib/swarm_sdk/plugin_registry.rb +101 -0
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +243 -0
- data/lib/swarm_sdk/providers/openai_with_responses.rb +582 -0
- data/lib/swarm_sdk/result.rb +97 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +334 -0
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +140 -0
- data/lib/swarm_sdk/swarm/builder.rb +586 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +151 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +419 -0
- data/lib/swarm_sdk/swarm.rb +982 -0
- data/lib/swarm_sdk/tools/bash.rb +274 -0
- data/lib/swarm_sdk/tools/clock.rb +44 -0
- data/lib/swarm_sdk/tools/delegate.rb +164 -0
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +83 -0
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +99 -0
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +101 -0
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +78 -0
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +194 -0
- data/lib/swarm_sdk/tools/edit.rb +150 -0
- data/lib/swarm_sdk/tools/glob.rb +158 -0
- data/lib/swarm_sdk/tools/grep.rb +228 -0
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +43 -0
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +163 -0
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +65 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +232 -0
- data/lib/swarm_sdk/tools/path_resolver.rb +43 -0
- data/lib/swarm_sdk/tools/read.rb +251 -0
- data/lib/swarm_sdk/tools/registry.rb +93 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +96 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +76 -0
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +91 -0
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +61 -0
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +224 -0
- data/lib/swarm_sdk/tools/stores/storage.rb +148 -0
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +65 -0
- data/lib/swarm_sdk/tools/think.rb +95 -0
- data/lib/swarm_sdk/tools/todo_write.rb +216 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +261 -0
- data/lib/swarm_sdk/tools/write.rb +117 -0
- data/lib/swarm_sdk/utils.rb +50 -0
- data/lib/swarm_sdk/version.rb +5 -0
- data/lib/swarm_sdk.rb +157 -0
- data/llm.v2.txt +13407 -0
- data/rubocop/cop/security/no_reflection_methods.rb +47 -0
- data/rubocop/cop/security/no_ruby_llm_logger.rb +32 -0
- data/swarm_cli.gemspec +57 -0
- data/swarm_memory.gemspec +28 -0
- data/swarm_sdk.gemspec +41 -0
- data/team.yml +1 -1
- data/team_full.yml +1875 -0
- data/{team_v2.yml → team_sdk.yml} +121 -52
- metadata +247 -4
- data/EXAMPLES.md +0 -164
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
class Swarm
|
|
5
|
+
# Builder provides a beautiful Ruby DSL for building swarms
|
|
6
|
+
#
|
|
7
|
+
# The DSL combines YAML simplicity with Ruby power, enabling:
|
|
8
|
+
# - Fluent, chainable configuration
|
|
9
|
+
# - Hooks as Ruby blocks OR shell commands
|
|
10
|
+
# - Full Ruby language features (variables, conditionals, loops)
|
|
11
|
+
# - Type-safe, IDE-friendly API
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# swarm = SwarmSDK.build do
|
|
15
|
+
# name "Dev Team"
|
|
16
|
+
# lead :backend
|
|
17
|
+
#
|
|
18
|
+
# agent :backend do
|
|
19
|
+
# model "gpt-5"
|
|
20
|
+
# prompt "You build APIs"
|
|
21
|
+
# tools :Read, :Write, :Bash
|
|
22
|
+
#
|
|
23
|
+
# # Hook as Ruby block - inline logic!
|
|
24
|
+
# hook :pre_tool_use, matcher: "Bash" do |ctx|
|
|
25
|
+
# SwarmSDK::Hooks::Result.halt("Blocked!") if ctx.tool_call.parameters[:command].include?("rm -rf")
|
|
26
|
+
# end
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# swarm.execute("Build auth API")
|
|
31
|
+
class Builder
|
|
32
|
+
# Main entry point for DSL
|
|
33
|
+
#
|
|
34
|
+
# @example
|
|
35
|
+
# swarm = SwarmSDK.build do
|
|
36
|
+
# name "Team"
|
|
37
|
+
# agent :backend { ... }
|
|
38
|
+
# end
|
|
39
|
+
class << self
|
|
40
|
+
def build(&block)
|
|
41
|
+
builder = new
|
|
42
|
+
builder.instance_eval(&block)
|
|
43
|
+
builder.build_swarm
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
@swarm_name = nil
|
|
49
|
+
@lead_agent = nil
|
|
50
|
+
@agents = {}
|
|
51
|
+
@all_agents_config = nil
|
|
52
|
+
@swarm_hooks = []
|
|
53
|
+
@nodes = {}
|
|
54
|
+
@start_node = nil
|
|
55
|
+
@scratchpad_enabled = true # Default: enabled
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Set swarm name
|
|
59
|
+
def name(swarm_name)
|
|
60
|
+
@swarm_name = swarm_name
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Set lead agent
|
|
64
|
+
def lead(agent_name)
|
|
65
|
+
@lead_agent = agent_name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Enable or disable shared scratchpad
|
|
69
|
+
#
|
|
70
|
+
# @param enabled [Boolean] Whether to enable scratchpad tools
|
|
71
|
+
def use_scratchpad(enabled)
|
|
72
|
+
@scratchpad_enabled = enabled
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Define an agent with fluent API or load from markdown content
|
|
76
|
+
#
|
|
77
|
+
# Supports two forms:
|
|
78
|
+
# 1. Inline DSL: agent :name do ... end
|
|
79
|
+
# 2. Markdown content: agent :name, <<~MD ... MD
|
|
80
|
+
#
|
|
81
|
+
# The name parameter is always required. If the markdown has a name field
|
|
82
|
+
# in frontmatter, it will be replaced by the name parameter.
|
|
83
|
+
#
|
|
84
|
+
# @example Inline DSL
|
|
85
|
+
# agent :backend do
|
|
86
|
+
# model "gpt-5"
|
|
87
|
+
# system_prompt "You build APIs"
|
|
88
|
+
# tools :Read, :Write
|
|
89
|
+
#
|
|
90
|
+
# hook :pre_tool_use, matcher: "Bash" do |ctx|
|
|
91
|
+
# # Inline validation logic!
|
|
92
|
+
# end
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# @example Markdown content
|
|
96
|
+
# agent :backend, <<~MD
|
|
97
|
+
# ---
|
|
98
|
+
# description: "Backend developer"
|
|
99
|
+
# model: "gpt-4"
|
|
100
|
+
# ---
|
|
101
|
+
#
|
|
102
|
+
# You build APIs.
|
|
103
|
+
# MD
|
|
104
|
+
def agent(name, content = nil, &block)
|
|
105
|
+
# Case 1: agent :name, <<~MD do ... end (markdown + overrides)
|
|
106
|
+
if content.is_a?(String) && block_given? && markdown_content?(content)
|
|
107
|
+
load_agent_from_markdown_with_overrides(content, name, &block)
|
|
108
|
+
# Case 2: agent :name, <<~MD (markdown only)
|
|
109
|
+
elsif content.is_a?(String) && !block_given? && markdown_content?(content)
|
|
110
|
+
load_agent_from_markdown(content, name)
|
|
111
|
+
# Case 3: agent :name do ... end (inline DSL)
|
|
112
|
+
elsif block_given?
|
|
113
|
+
builder = Agent::Builder.new(name)
|
|
114
|
+
builder.instance_eval(&block)
|
|
115
|
+
@agents[name] = builder
|
|
116
|
+
else
|
|
117
|
+
raise ArgumentError, "Invalid agent definition. Use: agent :name { ... } OR agent :name, <<~MD ... MD OR agent :name, <<~MD do ... end"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Add swarm-level hook (swarm_start, swarm_stop only)
|
|
122
|
+
#
|
|
123
|
+
# @example Shell command
|
|
124
|
+
# hook :swarm_start, command: "echo 'Starting' >> log.txt"
|
|
125
|
+
#
|
|
126
|
+
# @example Ruby block
|
|
127
|
+
# hook :swarm_start do |ctx|
|
|
128
|
+
# puts "Swarm starting: #{ctx.metadata[:prompt]}"
|
|
129
|
+
# end
|
|
130
|
+
def hook(event, command: nil, timeout: nil, &block)
|
|
131
|
+
# Validate swarm-level events
|
|
132
|
+
unless [:swarm_start, :swarm_stop].include?(event)
|
|
133
|
+
raise ArgumentError, "Invalid swarm-level hook: #{event}. Only :swarm_start and :swarm_stop allowed at swarm level. Use all_agents { hook ... } or agent { hook ... } for other events."
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
@swarm_hooks << { event: event, command: command, timeout: timeout, block: block }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Configure all agents with a block
|
|
140
|
+
#
|
|
141
|
+
# @example
|
|
142
|
+
# all_agents do
|
|
143
|
+
# tools :Read, :Write
|
|
144
|
+
#
|
|
145
|
+
# hook :pre_tool_use, matcher: "Write" do |ctx|
|
|
146
|
+
# # Validation for all agents
|
|
147
|
+
# end
|
|
148
|
+
# end
|
|
149
|
+
def all_agents(&block)
|
|
150
|
+
builder = AllAgentsBuilder.new
|
|
151
|
+
builder.instance_eval(&block)
|
|
152
|
+
@all_agents_config = builder
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Define a node (mini-swarm execution stage)
|
|
156
|
+
#
|
|
157
|
+
# Nodes enable multi-stage workflows where different agent teams
|
|
158
|
+
# collaborate in sequence. Each node is an independent swarm execution.
|
|
159
|
+
#
|
|
160
|
+
# @param name [Symbol] Node name
|
|
161
|
+
# @yield Block for node configuration
|
|
162
|
+
# @return [void]
|
|
163
|
+
#
|
|
164
|
+
# @example Solo agent node
|
|
165
|
+
# node :planning do
|
|
166
|
+
# agent(:architect)
|
|
167
|
+
# end
|
|
168
|
+
#
|
|
169
|
+
# @example Multi-agent node with delegation
|
|
170
|
+
# node :implementation do
|
|
171
|
+
# agent(:backend).delegates_to(:tester, :database)
|
|
172
|
+
# agent(:tester).delegates_to(:database)
|
|
173
|
+
# agent(:database)
|
|
174
|
+
# after :planning
|
|
175
|
+
# end
|
|
176
|
+
def node(name, &block)
|
|
177
|
+
builder = Node::Builder.new(name)
|
|
178
|
+
builder.instance_eval(&block)
|
|
179
|
+
@nodes[name] = builder
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Set the starting node for workflow execution
|
|
183
|
+
#
|
|
184
|
+
# Required when nodes are defined. Specifies which node to execute first.
|
|
185
|
+
#
|
|
186
|
+
# @param name [Symbol] Name of starting node
|
|
187
|
+
# @return [void]
|
|
188
|
+
#
|
|
189
|
+
# @example
|
|
190
|
+
# start_node :planning
|
|
191
|
+
def start_node(name)
|
|
192
|
+
@start_node = name.to_sym
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Build the actual Swarm instance or NodeOrchestrator
|
|
196
|
+
def build_swarm
|
|
197
|
+
raise ConfigurationError, "Swarm name not set. Use: name 'My Swarm'" unless @swarm_name
|
|
198
|
+
|
|
199
|
+
# Check if nodes are defined
|
|
200
|
+
if @nodes.any?
|
|
201
|
+
# Node-based workflow (agents optional for agent-less workflows)
|
|
202
|
+
build_node_orchestrator
|
|
203
|
+
else
|
|
204
|
+
# Traditional single-swarm execution (requires agents and lead)
|
|
205
|
+
raise ConfigurationError, "No agents defined. Use: agent :name { ... }" if @agents.empty?
|
|
206
|
+
raise ConfigurationError, "Lead agent not set. Use: lead :agent_name" unless @lead_agent
|
|
207
|
+
|
|
208
|
+
build_single_swarm
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
private
|
|
213
|
+
|
|
214
|
+
# Check if a string is markdown content (has frontmatter)
|
|
215
|
+
#
|
|
216
|
+
# @param str [String] String to check
|
|
217
|
+
# @return [Boolean] true if string contains markdown frontmatter
|
|
218
|
+
def markdown_content?(str)
|
|
219
|
+
str.start_with?("---") || str.include?("\n---\n")
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Load an agent from markdown content
|
|
223
|
+
#
|
|
224
|
+
# Returns a hash of the agent config (not a Definition yet) so that
|
|
225
|
+
# all_agents config can be applied later in the build process.
|
|
226
|
+
#
|
|
227
|
+
# @param content [String] Markdown content with frontmatter
|
|
228
|
+
# @param name_override [Symbol, nil] Optional name to override frontmatter name
|
|
229
|
+
# @return [void]
|
|
230
|
+
def load_agent_from_markdown(content, name_override = nil)
|
|
231
|
+
# Parse markdown content - will extract name from frontmatter if not overridden
|
|
232
|
+
definition = MarkdownParser.parse(content, name_override)
|
|
233
|
+
|
|
234
|
+
# Store the config hash (not Definition) so all_agents can be applied
|
|
235
|
+
# We'll wrap this in a special marker so we know it came from markdown
|
|
236
|
+
@agents[definition.name] = { __file_config__: definition.to_h }
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Load an agent from markdown content with DSL overrides
|
|
240
|
+
#
|
|
241
|
+
# This allows loading from a file and then overriding specific settings:
|
|
242
|
+
# agent :reviewer, File.read("reviewer.md") do
|
|
243
|
+
# provider :openai
|
|
244
|
+
# model "gpt-4o"
|
|
245
|
+
# end
|
|
246
|
+
#
|
|
247
|
+
# @param content [String] Markdown content with frontmatter
|
|
248
|
+
# @param name_override [Symbol, nil] Optional name to override frontmatter name
|
|
249
|
+
# @yield Block with DSL overrides
|
|
250
|
+
# @return [void]
|
|
251
|
+
def load_agent_from_markdown_with_overrides(content, name_override = nil, &block)
|
|
252
|
+
# Parse markdown content first
|
|
253
|
+
definition = MarkdownParser.parse(content, name_override)
|
|
254
|
+
|
|
255
|
+
# Create a builder with the markdown config
|
|
256
|
+
builder = Agent::Builder.new(definition.name)
|
|
257
|
+
|
|
258
|
+
# Apply markdown settings to builder (these become the base)
|
|
259
|
+
apply_definition_to_builder(builder, definition.to_h)
|
|
260
|
+
|
|
261
|
+
# Apply DSL overrides (these override the markdown settings)
|
|
262
|
+
builder.instance_eval(&block)
|
|
263
|
+
|
|
264
|
+
# Store the builder (not file config) so overrides are preserved
|
|
265
|
+
@agents[definition.name] = builder
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Apply agent definition hash to a builder
|
|
269
|
+
#
|
|
270
|
+
# @param builder [Agent::Builder] Builder to configure
|
|
271
|
+
# @param config [Hash] Configuration hash from definition
|
|
272
|
+
# @return [void]
|
|
273
|
+
def apply_definition_to_builder(builder, config)
|
|
274
|
+
builder.description(config[:description]) if config[:description]
|
|
275
|
+
builder.model(config[:model]) if config[:model]
|
|
276
|
+
builder.provider(config[:provider]) if config[:provider]
|
|
277
|
+
builder.base_url(config[:base_url]) if config[:base_url]
|
|
278
|
+
builder.api_version(config[:api_version]) if config[:api_version]
|
|
279
|
+
builder.context_window(config[:context_window]) if config[:context_window]
|
|
280
|
+
builder.system_prompt(config[:system_prompt]) if config[:system_prompt]
|
|
281
|
+
builder.directory(config[:directory]) if config[:directory]
|
|
282
|
+
builder.timeout(config[:timeout]) if config[:timeout]
|
|
283
|
+
builder.parameters(config[:parameters]) if config[:parameters]
|
|
284
|
+
builder.headers(config[:headers]) if config[:headers]
|
|
285
|
+
builder.coding_agent(config[:coding_agent]) unless config[:coding_agent].nil?
|
|
286
|
+
# Don't apply assume_model_exists from markdown - let DSL overrides or auto-enable handle it
|
|
287
|
+
# builder.assume_model_exists(config[:assume_model_exists]) unless config[:assume_model_exists].nil?
|
|
288
|
+
builder.bypass_permissions(config[:bypass_permissions]) if config[:bypass_permissions]
|
|
289
|
+
builder.disable_default_tools(config[:disable_default_tools]) unless config[:disable_default_tools].nil?
|
|
290
|
+
|
|
291
|
+
# Add tools from markdown
|
|
292
|
+
if config[:tools]&.any?
|
|
293
|
+
# Extract tool names from the tools array (which may be hashes with permissions)
|
|
294
|
+
tool_names = config[:tools].map do |tool|
|
|
295
|
+
tool.is_a?(Hash) ? tool[:name] : tool
|
|
296
|
+
end
|
|
297
|
+
builder.tools(*tool_names)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Add delegates_to
|
|
301
|
+
builder.delegates_to(*config[:delegates_to]) if config[:delegates_to]&.any?
|
|
302
|
+
|
|
303
|
+
# Add MCP servers
|
|
304
|
+
config[:mcp_servers]&.each do |server|
|
|
305
|
+
builder.mcp_server(server[:name], **server.except(:name))
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Build a traditional single-swarm execution
|
|
310
|
+
#
|
|
311
|
+
# @return [Swarm] Configured swarm instance
|
|
312
|
+
def build_single_swarm
|
|
313
|
+
# Create swarm using SDK
|
|
314
|
+
swarm = Swarm.new(name: @swarm_name, scratchpad_enabled: @scratchpad_enabled)
|
|
315
|
+
|
|
316
|
+
# Merge all_agents config into each agent (including file-loaded ones)
|
|
317
|
+
merge_all_agents_config_into_agents if @all_agents_config
|
|
318
|
+
|
|
319
|
+
# Build definitions and add to swarm
|
|
320
|
+
# Handle both Agent::Builder (inline DSL) and file configs (from files)
|
|
321
|
+
@agents.each do |agent_name, agent_builder_or_config|
|
|
322
|
+
definition = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
323
|
+
# File-loaded agent config (with all_agents merged)
|
|
324
|
+
Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
|
|
325
|
+
else
|
|
326
|
+
# Builder object (from inline DSL) - convert to definition
|
|
327
|
+
agent_builder_or_config.to_definition
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
swarm.add_agent(definition)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Set lead
|
|
334
|
+
swarm.lead = @lead_agent
|
|
335
|
+
|
|
336
|
+
# Apply swarm hooks (Ruby blocks)
|
|
337
|
+
# These are swarm-level hooks (swarm_start, swarm_stop)
|
|
338
|
+
@swarm_hooks.each do |hook_config|
|
|
339
|
+
apply_swarm_hook(swarm, hook_config)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Apply all_agents hooks (Ruby blocks)
|
|
343
|
+
# These become swarm-level default callbacks that apply to all agents
|
|
344
|
+
@all_agents_config&.hooks&.each do |hook_config|
|
|
345
|
+
apply_all_agents_hook(swarm, hook_config)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# NOTE: Agent-specific hooks are already stored in Agent::Definition.callbacks
|
|
349
|
+
# They'll be applied automatically during agent initialization (pass_4_configure_hooks)
|
|
350
|
+
# This ensures they're applied at the right time, after LogStream is set up
|
|
351
|
+
|
|
352
|
+
swarm
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
# Build a node-based workflow orchestrator
|
|
356
|
+
#
|
|
357
|
+
# @return [NodeOrchestrator] Configured orchestrator
|
|
358
|
+
def build_node_orchestrator
|
|
359
|
+
raise ConfigurationError, "start_node required when nodes are defined. Use: start_node :name" unless @start_node
|
|
360
|
+
|
|
361
|
+
# Merge all_agents config into each agent (applies to all nodes)
|
|
362
|
+
merge_all_agents_config_into_agents if @all_agents_config
|
|
363
|
+
|
|
364
|
+
# Build agent definitions
|
|
365
|
+
# Handle both Agent::Builder (inline DSL) and file configs (from files)
|
|
366
|
+
agent_definitions = {}
|
|
367
|
+
@agents.each do |agent_name, agent_builder_or_config|
|
|
368
|
+
agent_definitions[agent_name] = if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
369
|
+
# File-loaded agent config (with all_agents merged)
|
|
370
|
+
Agent::Definition.new(agent_name, agent_builder_or_config[:__file_config__])
|
|
371
|
+
else
|
|
372
|
+
# Builder object (from inline DSL) - convert to definition
|
|
373
|
+
agent_builder_or_config.to_definition
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Create node orchestrator
|
|
378
|
+
NodeOrchestrator.new(
|
|
379
|
+
swarm_name: @swarm_name,
|
|
380
|
+
agent_definitions: agent_definitions,
|
|
381
|
+
nodes: @nodes,
|
|
382
|
+
start_node: @start_node,
|
|
383
|
+
)
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
# Merge all_agents configuration into each agent
|
|
387
|
+
#
|
|
388
|
+
# All_agents values are used as defaults - agent-specific values override.
|
|
389
|
+
# This applies to both inline DSL agents (Builder) and file-loaded agents (config hash).
|
|
390
|
+
#
|
|
391
|
+
# @return [void]
|
|
392
|
+
def merge_all_agents_config_into_agents
|
|
393
|
+
return unless @all_agents_config
|
|
394
|
+
|
|
395
|
+
all_agents_hash = @all_agents_config.to_h
|
|
396
|
+
|
|
397
|
+
@agents.each_value do |agent_builder_or_config|
|
|
398
|
+
if agent_builder_or_config.is_a?(Hash) && agent_builder_or_config.key?(:__file_config__)
|
|
399
|
+
# File-loaded agent - merge into the config hash
|
|
400
|
+
file_config = agent_builder_or_config[:__file_config__]
|
|
401
|
+
|
|
402
|
+
# Merge all_agents into file config (file config overrides)
|
|
403
|
+
# Use same merge strategy as Configuration class
|
|
404
|
+
merged_config = merge_all_agents_into_config(all_agents_hash, file_config)
|
|
405
|
+
|
|
406
|
+
# Update the stored config
|
|
407
|
+
agent_builder_or_config[:__file_config__] = merged_config
|
|
408
|
+
else
|
|
409
|
+
# Builder object (inline DSL agent)
|
|
410
|
+
agent_builder = agent_builder_or_config
|
|
411
|
+
|
|
412
|
+
# Apply all_agents defaults that haven't been set at agent level
|
|
413
|
+
# Agent values override all_agents values
|
|
414
|
+
apply_all_agents_defaults(agent_builder, all_agents_hash)
|
|
415
|
+
|
|
416
|
+
# Merge tools (prepend all_agents tools)
|
|
417
|
+
all_agents_tools = @all_agents_config.tools_list
|
|
418
|
+
agent_builder.prepend_tools(*all_agents_tools) if all_agents_tools.any?
|
|
419
|
+
|
|
420
|
+
# Pass all_agents permissions as default_permissions
|
|
421
|
+
if @all_agents_config.permissions_config.any?
|
|
422
|
+
agent_builder.default_permissions = @all_agents_config.permissions_config
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# Merge all_agents config into file-loaded agent config
|
|
429
|
+
#
|
|
430
|
+
# Follows same merge strategy as Configuration class:
|
|
431
|
+
# - Arrays (tools, delegates_to): Concatenate (all_agents + file)
|
|
432
|
+
# - Hashes (parameters, headers): Merge (file values override)
|
|
433
|
+
# - Scalars (model, provider, etc.): File overrides
|
|
434
|
+
#
|
|
435
|
+
# @param all_agents_hash [Hash] All_agents configuration
|
|
436
|
+
# @param file_config [Hash] File-loaded agent configuration
|
|
437
|
+
# @return [Hash] Merged configuration
|
|
438
|
+
def merge_all_agents_into_config(all_agents_hash, file_config)
|
|
439
|
+
merged = all_agents_hash.dup
|
|
440
|
+
|
|
441
|
+
file_config.each do |key, value|
|
|
442
|
+
case key
|
|
443
|
+
when :tools
|
|
444
|
+
# Concatenate tools: all_agents.tools + file.tools
|
|
445
|
+
merged[:tools] = Array(merged[:tools]) + Array(value)
|
|
446
|
+
when :delegates_to
|
|
447
|
+
# Concatenate delegates_to
|
|
448
|
+
merged[:delegates_to] = Array(merged[:delegates_to]) + Array(value)
|
|
449
|
+
when :parameters
|
|
450
|
+
# Merge parameters: file values override all_agents
|
|
451
|
+
merged[:parameters] = (merged[:parameters] || {}).merge(value || {})
|
|
452
|
+
when :headers
|
|
453
|
+
# Merge headers: file values override all_agents
|
|
454
|
+
merged[:headers] = (merged[:headers] || {}).merge(value || {})
|
|
455
|
+
else
|
|
456
|
+
# For everything else, file value overrides all_agents value
|
|
457
|
+
merged[key] = value
|
|
458
|
+
end
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
# Pass all_agents permissions as default_permissions
|
|
462
|
+
if all_agents_hash[:permissions] && !merged[:default_permissions]
|
|
463
|
+
merged[:default_permissions] = all_agents_hash[:permissions]
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
merged
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Apply all_agents defaults to an agent builder
|
|
470
|
+
#
|
|
471
|
+
# Only sets values that haven't been explicitly set at the agent level.
|
|
472
|
+
# This implements the override semantics: agent values take precedence.
|
|
473
|
+
#
|
|
474
|
+
# @param agent_builder [Agent::Builder] The agent builder to configure
|
|
475
|
+
# @param all_agents_hash [Hash] All_agents configuration
|
|
476
|
+
# @return [void]
|
|
477
|
+
def apply_all_agents_defaults(agent_builder, all_agents_hash)
|
|
478
|
+
# Model: only set if agent hasn't explicitly set it
|
|
479
|
+
if all_agents_hash[:model] && !agent_builder.model_set?
|
|
480
|
+
agent_builder.model(all_agents_hash[:model])
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
# Provider: only set if agent hasn't set it
|
|
484
|
+
if all_agents_hash[:provider] && !agent_builder.provider_set?
|
|
485
|
+
agent_builder.provider(all_agents_hash[:provider])
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
# Base URL: only set if agent hasn't set it
|
|
489
|
+
if all_agents_hash[:base_url] && !agent_builder.base_url_set?
|
|
490
|
+
agent_builder.base_url(all_agents_hash[:base_url])
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
# API Version: only set if agent hasn't set it
|
|
494
|
+
if all_agents_hash[:api_version] && !agent_builder.api_version_set?
|
|
495
|
+
agent_builder.api_version(all_agents_hash[:api_version])
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
# Timeout: only set if agent hasn't set it
|
|
499
|
+
if all_agents_hash[:timeout] && !agent_builder.timeout_set?
|
|
500
|
+
agent_builder.timeout(all_agents_hash[:timeout])
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Parameters: merge (all_agents + agent, agent values override)
|
|
504
|
+
if all_agents_hash[:parameters]
|
|
505
|
+
merged_params = all_agents_hash[:parameters].merge(agent_builder.parameters)
|
|
506
|
+
agent_builder.parameters(merged_params)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
# Headers: merge (all_agents + agent, agent values override)
|
|
510
|
+
if all_agents_hash[:headers]
|
|
511
|
+
merged_headers = all_agents_hash[:headers].merge(agent_builder.headers)
|
|
512
|
+
agent_builder.headers(merged_headers)
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
# Coding_agent: only set if agent hasn't set it
|
|
516
|
+
if !all_agents_hash[:coding_agent].nil? && !agent_builder.coding_agent_set?
|
|
517
|
+
agent_builder.coding_agent(all_agents_hash[:coding_agent])
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def apply_swarm_hook(swarm, config)
|
|
522
|
+
event = config[:event]
|
|
523
|
+
|
|
524
|
+
if config[:block]
|
|
525
|
+
# Ruby block hook - register directly
|
|
526
|
+
swarm.add_default_callback(event, &config[:block])
|
|
527
|
+
elsif config[:command]
|
|
528
|
+
# Shell command hook - use ShellExecutor
|
|
529
|
+
swarm.add_default_callback(event) do |context|
|
|
530
|
+
input_json = build_hook_input(context, event)
|
|
531
|
+
Hooks::ShellExecutor.execute(
|
|
532
|
+
command: config[:command],
|
|
533
|
+
input_json: input_json,
|
|
534
|
+
timeout: config[:timeout] || 60,
|
|
535
|
+
swarm_name: swarm.name,
|
|
536
|
+
event: event,
|
|
537
|
+
)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def apply_all_agents_hook(swarm, config)
|
|
543
|
+
event = config[:event]
|
|
544
|
+
matcher = config[:matcher]
|
|
545
|
+
|
|
546
|
+
if config[:block]
|
|
547
|
+
# Ruby block hook
|
|
548
|
+
swarm.add_default_callback(event, matcher: matcher, &config[:block])
|
|
549
|
+
elsif config[:command]
|
|
550
|
+
# Shell command hook
|
|
551
|
+
swarm.add_default_callback(event, matcher: matcher) do |context|
|
|
552
|
+
input_json = build_hook_input(context, event)
|
|
553
|
+
Hooks::ShellExecutor.execute(
|
|
554
|
+
command: config[:command],
|
|
555
|
+
input_json: input_json,
|
|
556
|
+
timeout: config[:timeout] || 60,
|
|
557
|
+
agent_name: context.agent_name,
|
|
558
|
+
swarm_name: swarm.name,
|
|
559
|
+
event: event,
|
|
560
|
+
)
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
def build_hook_input(context, event)
|
|
566
|
+
# Build JSON input for shell hooks (similar to HooksAdapter)
|
|
567
|
+
base = { event: event.to_s }
|
|
568
|
+
|
|
569
|
+
case event
|
|
570
|
+
when :pre_tool_use
|
|
571
|
+
base.merge(tool: context.tool_call.name, parameters: context.tool_call.parameters)
|
|
572
|
+
when :post_tool_use
|
|
573
|
+
base.merge(result: context.tool_result.content, success: context.tool_result.success?)
|
|
574
|
+
when :user_prompt
|
|
575
|
+
base.merge(prompt: context.metadata[:prompt])
|
|
576
|
+
when :swarm_start
|
|
577
|
+
base.merge(prompt: context.metadata[:prompt])
|
|
578
|
+
when :swarm_stop
|
|
579
|
+
base.merge(success: context.metadata[:success], duration: context.metadata[:duration])
|
|
580
|
+
else
|
|
581
|
+
base
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
end
|