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,248 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "json"
|
|
5
|
+
require "timeout"
|
|
6
|
+
|
|
7
|
+
module SwarmSDK
|
|
8
|
+
module Node
|
|
9
|
+
# Executes bash command transformers for node input/output transformation
|
|
10
|
+
#
|
|
11
|
+
# Transformers are shell commands that receive NodeContext data on STDIN as JSON
|
|
12
|
+
# and produce transformed content on STDOUT.
|
|
13
|
+
#
|
|
14
|
+
# ## Exit Code Behavior
|
|
15
|
+
#
|
|
16
|
+
# - **Exit 0**: Transform success
|
|
17
|
+
# - Use STDOUT as the transformed content
|
|
18
|
+
# - Node execution proceeds with transformed content
|
|
19
|
+
#
|
|
20
|
+
# - **Exit 1**: Skip node execution (pass-through)
|
|
21
|
+
# - STDOUT is IGNORED
|
|
22
|
+
# - Input transformer: Use current_input unchanged (no transformation)
|
|
23
|
+
# - Output transformer: Use result.content unchanged (no transformation)
|
|
24
|
+
# - For input transformer: Also skips the node's LLM execution
|
|
25
|
+
#
|
|
26
|
+
# - **Exit 2**: Halt entire workflow
|
|
27
|
+
# - STDOUT is IGNORED
|
|
28
|
+
# - STDERR is shown as error message
|
|
29
|
+
# - Workflow stops immediately with error
|
|
30
|
+
#
|
|
31
|
+
# ## JSON Input Format (STDIN)
|
|
32
|
+
#
|
|
33
|
+
# **Input transformer receives:**
|
|
34
|
+
# ```json
|
|
35
|
+
# {
|
|
36
|
+
# "event": "input",
|
|
37
|
+
# "node": "implementation",
|
|
38
|
+
# "original_prompt": "Build auth API",
|
|
39
|
+
# "content": "PLAN: Create endpoints...",
|
|
40
|
+
# "all_results": {
|
|
41
|
+
# "planning": {
|
|
42
|
+
# "content": "Create endpoints...",
|
|
43
|
+
# "agent": "planner",
|
|
44
|
+
# "duration": 2.5,
|
|
45
|
+
# "success": true
|
|
46
|
+
# }
|
|
47
|
+
# },
|
|
48
|
+
# "dependencies": ["planning"]
|
|
49
|
+
# }
|
|
50
|
+
# ```
|
|
51
|
+
#
|
|
52
|
+
# **Output transformer receives:**
|
|
53
|
+
# ```json
|
|
54
|
+
# {
|
|
55
|
+
# "event": "output",
|
|
56
|
+
# "node": "implementation",
|
|
57
|
+
# "original_prompt": "Build auth API",
|
|
58
|
+
# "content": "Implementation complete",
|
|
59
|
+
# "all_results": {
|
|
60
|
+
# "planning": {...},
|
|
61
|
+
# "implementation": {...}
|
|
62
|
+
# }
|
|
63
|
+
# }
|
|
64
|
+
# ```
|
|
65
|
+
#
|
|
66
|
+
# @example Input transformer that validates
|
|
67
|
+
# # validate.sh
|
|
68
|
+
# #!/bin/bash
|
|
69
|
+
# INPUT=$(cat)
|
|
70
|
+
# CONTENT=$(echo "$INPUT" | jq -r '.content')
|
|
71
|
+
#
|
|
72
|
+
# if [ ${#CONTENT} -gt 10000 ]; then
|
|
73
|
+
# echo "Content too long" >&2
|
|
74
|
+
# exit 2 # Halt workflow
|
|
75
|
+
# fi
|
|
76
|
+
#
|
|
77
|
+
# echo "$CONTENT"
|
|
78
|
+
# exit 0
|
|
79
|
+
#
|
|
80
|
+
# @example Input transformer that caches (skip execution)
|
|
81
|
+
# # cache_check.sh
|
|
82
|
+
# #!/bin/bash
|
|
83
|
+
# INPUT=$(cat)
|
|
84
|
+
# CONTENT=$(echo "$INPUT" | jq -r '.content')
|
|
85
|
+
#
|
|
86
|
+
# if cached "$CONTENT"; then
|
|
87
|
+
# exit 1 # Skip node execution, pass through unchanged
|
|
88
|
+
# fi
|
|
89
|
+
#
|
|
90
|
+
# echo "$CONTENT"
|
|
91
|
+
# exit 0
|
|
92
|
+
class TransformerExecutor
|
|
93
|
+
DEFAULT_TIMEOUT = 60
|
|
94
|
+
|
|
95
|
+
# Result object for transformer execution
|
|
96
|
+
TransformerResult = Struct.new(:success, :content, :skip_execution, :halt, :error_message, keyword_init: true) do
|
|
97
|
+
def skip_execution?
|
|
98
|
+
skip_execution
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def halt?
|
|
102
|
+
halt
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class << self
|
|
107
|
+
# Execute a transformer shell command
|
|
108
|
+
#
|
|
109
|
+
# @param command [String] Shell command to execute
|
|
110
|
+
# @param context [NodeContext] Node context for building JSON input
|
|
111
|
+
# @param event [String] Event type ("input" or "output")
|
|
112
|
+
# @param node_name [Symbol] Current node name
|
|
113
|
+
# @param fallback_content [String] Content to use if skip (exit 1)
|
|
114
|
+
# @param timeout [Integer] Timeout in seconds (default: 60)
|
|
115
|
+
# @return [TransformerResult] Result with transformed content or skip/halt flags
|
|
116
|
+
def execute(command:, context:, event:, node_name:, fallback_content:, timeout: DEFAULT_TIMEOUT)
|
|
117
|
+
# Build JSON input for transformer
|
|
118
|
+
input_json = build_transformer_input(context, event, node_name)
|
|
119
|
+
|
|
120
|
+
# Build environment variables
|
|
121
|
+
env = build_environment(node_name: node_name)
|
|
122
|
+
|
|
123
|
+
# Execute command with JSON stdin and timeout
|
|
124
|
+
stdout, stderr, status = Timeout.timeout(timeout) do
|
|
125
|
+
Open3.capture3(
|
|
126
|
+
env,
|
|
127
|
+
command,
|
|
128
|
+
stdin_data: JSON.generate(input_json),
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Handle exit code
|
|
133
|
+
# Exit 0: Transform success, use STDOUT
|
|
134
|
+
# Exit 1: Skip node execution, use fallback_content (IGNORE STDOUT)
|
|
135
|
+
# Exit 2: Halt workflow with error (IGNORE STDOUT)
|
|
136
|
+
case status.exitstatus
|
|
137
|
+
when 0
|
|
138
|
+
# Success: use STDOUT as transformed content (strip trailing newline)
|
|
139
|
+
TransformerResult.new(
|
|
140
|
+
success: true,
|
|
141
|
+
content: stdout.chomp, # Remove trailing newline from echo
|
|
142
|
+
skip_execution: false,
|
|
143
|
+
halt: false,
|
|
144
|
+
error_message: nil,
|
|
145
|
+
)
|
|
146
|
+
when 1
|
|
147
|
+
# Skip node execution: use fallback_content unchanged (IGNORE STDOUT)
|
|
148
|
+
# For input transformer: skip_execution = true (skip LLM call)
|
|
149
|
+
# For output transformer: skip_execution = false (just pass through)
|
|
150
|
+
TransformerResult.new(
|
|
151
|
+
success: true,
|
|
152
|
+
content: fallback_content,
|
|
153
|
+
skip_execution: (event == "input"), # Only skip LLM for input transformers
|
|
154
|
+
halt: false,
|
|
155
|
+
error_message: nil,
|
|
156
|
+
)
|
|
157
|
+
when 2
|
|
158
|
+
# Halt workflow: return error (IGNORE STDOUT)
|
|
159
|
+
error_msg = stderr.strip.empty? ? "Transformer halted workflow (exit 2)" : stderr.strip
|
|
160
|
+
TransformerResult.new(
|
|
161
|
+
success: false,
|
|
162
|
+
content: nil,
|
|
163
|
+
skip_execution: false,
|
|
164
|
+
halt: true,
|
|
165
|
+
error_message: error_msg,
|
|
166
|
+
)
|
|
167
|
+
else
|
|
168
|
+
# Unknown exit code: treat as error (halt)
|
|
169
|
+
error_msg = "Transformer exited with code #{status.exitstatus}\nSTDERR: #{stderr}"
|
|
170
|
+
TransformerResult.new(
|
|
171
|
+
success: false,
|
|
172
|
+
content: nil,
|
|
173
|
+
skip_execution: false,
|
|
174
|
+
halt: true,
|
|
175
|
+
error_message: error_msg,
|
|
176
|
+
)
|
|
177
|
+
end
|
|
178
|
+
rescue Timeout::Error
|
|
179
|
+
# Timeout: halt workflow
|
|
180
|
+
TransformerResult.new(
|
|
181
|
+
success: false,
|
|
182
|
+
content: nil,
|
|
183
|
+
skip_execution: false,
|
|
184
|
+
halt: true,
|
|
185
|
+
error_message: "Transformer command timed out after #{timeout}s",
|
|
186
|
+
)
|
|
187
|
+
rescue StandardError => e
|
|
188
|
+
# Execution error: halt workflow
|
|
189
|
+
TransformerResult.new(
|
|
190
|
+
success: false,
|
|
191
|
+
content: nil,
|
|
192
|
+
skip_execution: false,
|
|
193
|
+
halt: true,
|
|
194
|
+
error_message: "Transformer command failed: #{e.message}",
|
|
195
|
+
)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
|
|
200
|
+
# Build JSON input for transformer command
|
|
201
|
+
#
|
|
202
|
+
# @param context [NodeContext] Node context
|
|
203
|
+
# @param event [String] Event type ("input" or "output")
|
|
204
|
+
# @param node_name [Symbol] Node name
|
|
205
|
+
# @return [Hash] JSON data to pass on stdin
|
|
206
|
+
def build_transformer_input(context, event, node_name)
|
|
207
|
+
base = {
|
|
208
|
+
event: event,
|
|
209
|
+
node: node_name.to_s,
|
|
210
|
+
original_prompt: context.original_prompt,
|
|
211
|
+
content: context.content,
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Add all_results (convert Result objects to hashes)
|
|
215
|
+
if context.all_results && !context.all_results.empty?
|
|
216
|
+
base[:all_results] = context.all_results.transform_values do |result|
|
|
217
|
+
{
|
|
218
|
+
content: result.content,
|
|
219
|
+
agent: result.agent,
|
|
220
|
+
duration: result.duration,
|
|
221
|
+
success: result.success?,
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Add dependencies for input transformers
|
|
227
|
+
if event == "input" && context.dependencies
|
|
228
|
+
base[:dependencies] = context.dependencies.map(&:to_s)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
base
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Build environment variables for transformer execution
|
|
235
|
+
#
|
|
236
|
+
# @param node_name [Symbol] Current node name
|
|
237
|
+
# @return [Hash] Environment variables
|
|
238
|
+
def build_environment(node_name:)
|
|
239
|
+
{
|
|
240
|
+
"SWARM_SDK_PROJECT_DIR" => Dir.pwd,
|
|
241
|
+
"SWARM_SDK_NODE_NAME" => node_name.to_s,
|
|
242
|
+
"PATH" => ENV.fetch("PATH", ""),
|
|
243
|
+
}
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SwarmSDK
|
|
4
|
+
# NodeContext provides context information to node transformers
|
|
5
|
+
#
|
|
6
|
+
# This class is passed to input and output transformers, giving them access to:
|
|
7
|
+
# - The original user prompt
|
|
8
|
+
# - Results from all previous nodes
|
|
9
|
+
# - Current node metadata
|
|
10
|
+
# - Convenience accessors for common operations
|
|
11
|
+
#
|
|
12
|
+
# @example Input transformer
|
|
13
|
+
# input do |ctx|
|
|
14
|
+
# ctx.content # Previous node's content (convenience)
|
|
15
|
+
# ctx.original_prompt # Original user prompt
|
|
16
|
+
# ctx.all_results[:plan] # Access any previous node
|
|
17
|
+
# ctx.node_name # Current node name
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# @example Output transformer
|
|
21
|
+
# output do |ctx|
|
|
22
|
+
# ctx.content # Current result's content (convenience)
|
|
23
|
+
# ctx.original_prompt # Original user prompt
|
|
24
|
+
# ctx.all_results[:plan] # Access previous nodes
|
|
25
|
+
# end
|
|
26
|
+
class NodeContext
|
|
27
|
+
attr_reader :original_prompt, :all_results, :node_name, :dependencies
|
|
28
|
+
|
|
29
|
+
# For input transformers: result from previous node(s)
|
|
30
|
+
attr_reader :previous_result
|
|
31
|
+
|
|
32
|
+
# For output transformers: current node's result
|
|
33
|
+
attr_reader :result
|
|
34
|
+
|
|
35
|
+
class << self
|
|
36
|
+
# Create a NodeContext for input transformers
|
|
37
|
+
#
|
|
38
|
+
# @param previous_result [Result, Hash, String] Previous node's result or hash of results
|
|
39
|
+
# @param all_results [Hash<Symbol, Result>] Results from all completed nodes
|
|
40
|
+
# @param original_prompt [String] The original user prompt
|
|
41
|
+
# @param node_name [Symbol] Current node name
|
|
42
|
+
# @param dependencies [Array<Symbol>] Node dependencies
|
|
43
|
+
# @param transformed_content [String, nil] Already-transformed content from previous output transformer
|
|
44
|
+
# @return [NodeContext]
|
|
45
|
+
def for_input(previous_result:, all_results:, original_prompt:, node_name:, dependencies:, transformed_content: nil)
|
|
46
|
+
new(
|
|
47
|
+
previous_result: previous_result,
|
|
48
|
+
all_results: all_results,
|
|
49
|
+
original_prompt: original_prompt,
|
|
50
|
+
node_name: node_name,
|
|
51
|
+
dependencies: dependencies,
|
|
52
|
+
result: nil,
|
|
53
|
+
transformed_content: transformed_content,
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Create a NodeContext for output transformers
|
|
58
|
+
#
|
|
59
|
+
# @param result [Result] Current node's execution result
|
|
60
|
+
# @param all_results [Hash<Symbol, Result>] Results from all completed nodes (including current)
|
|
61
|
+
# @param original_prompt [String] The original user prompt
|
|
62
|
+
# @param node_name [Symbol] Current node name
|
|
63
|
+
# @return [NodeContext]
|
|
64
|
+
def for_output(result:, all_results:, original_prompt:, node_name:)
|
|
65
|
+
new(
|
|
66
|
+
result: result,
|
|
67
|
+
all_results: all_results,
|
|
68
|
+
original_prompt: original_prompt,
|
|
69
|
+
node_name: node_name,
|
|
70
|
+
dependencies: [],
|
|
71
|
+
previous_result: nil,
|
|
72
|
+
transformed_content: nil,
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def initialize(previous_result:, all_results:, original_prompt:, node_name:, dependencies:, result:, transformed_content:)
|
|
78
|
+
@previous_result = previous_result
|
|
79
|
+
@result = result
|
|
80
|
+
@all_results = all_results
|
|
81
|
+
@original_prompt = original_prompt
|
|
82
|
+
@node_name = node_name
|
|
83
|
+
@dependencies = dependencies
|
|
84
|
+
@transformed_content = transformed_content
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Convenience accessor: Get content from previous_result or result
|
|
88
|
+
#
|
|
89
|
+
# For input transformers:
|
|
90
|
+
# - Returns transformed_content if available (from previous output transformer)
|
|
91
|
+
# - Otherwise returns previous_result.content (original content)
|
|
92
|
+
# - Returns nil for multiple dependencies (use all_results instead)
|
|
93
|
+
# For output transformers: returns result.content
|
|
94
|
+
#
|
|
95
|
+
# @return [String, nil]
|
|
96
|
+
def content
|
|
97
|
+
if @result
|
|
98
|
+
# Output transformer context: return current result's content
|
|
99
|
+
@result.content
|
|
100
|
+
elsif @transformed_content
|
|
101
|
+
# Input transformer with transformed content from previous output
|
|
102
|
+
@transformed_content
|
|
103
|
+
elsif @previous_result.respond_to?(:content)
|
|
104
|
+
# Input transformer context with Result object (original content)
|
|
105
|
+
@previous_result.content
|
|
106
|
+
elsif @previous_result.is_a?(Hash)
|
|
107
|
+
# Input transformer with multiple dependencies (hash of results)
|
|
108
|
+
nil # No single "content" - user must pick from all_results hash
|
|
109
|
+
else
|
|
110
|
+
# String or other type (initial prompt, no dependencies)
|
|
111
|
+
@previous_result.to_s
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Convenience accessor: Get agent from previous_result or result
|
|
116
|
+
#
|
|
117
|
+
# @return [String, nil]
|
|
118
|
+
def agent
|
|
119
|
+
if @result
|
|
120
|
+
@result.agent
|
|
121
|
+
elsif @previous_result.respond_to?(:agent)
|
|
122
|
+
@previous_result.agent
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Convenience accessor: Get logs from previous_result or result
|
|
127
|
+
#
|
|
128
|
+
# @return [Array, nil]
|
|
129
|
+
def logs
|
|
130
|
+
if @result
|
|
131
|
+
@result.logs
|
|
132
|
+
elsif @previous_result.respond_to?(:logs)
|
|
133
|
+
@previous_result.logs
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Convenience accessor: Get duration from previous_result or result
|
|
138
|
+
#
|
|
139
|
+
# @return [Float, nil]
|
|
140
|
+
def duration
|
|
141
|
+
if @result
|
|
142
|
+
@result.duration
|
|
143
|
+
elsif @previous_result.respond_to?(:duration)
|
|
144
|
+
@previous_result.duration
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Convenience accessor: Get error from previous_result or result
|
|
149
|
+
#
|
|
150
|
+
# @return [Exception, nil]
|
|
151
|
+
def error
|
|
152
|
+
if @result
|
|
153
|
+
@result.error
|
|
154
|
+
elsif @previous_result.respond_to?(:error)
|
|
155
|
+
@previous_result.error
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Convenience accessor: Check success status
|
|
160
|
+
#
|
|
161
|
+
# @return [Boolean, nil]
|
|
162
|
+
def success?
|
|
163
|
+
if @result
|
|
164
|
+
@result.success?
|
|
165
|
+
elsif @previous_result.respond_to?(:success?)
|
|
166
|
+
@previous_result.success?
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|