swarm_sdk 2.7.13 → 3.0.0.alpha1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -1,476 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- # Restores swarm/workflow conversation state from snapshots
5
- #
6
- # Unified implementation that works for both Swarm and Workflow.
7
- # Validates compatibility between snapshot and current configuration,
8
- # restores conversation history, context state, scratchpad contents, and
9
- # read tracking information.
10
- #
11
- # Handles configuration mismatches gracefully by skipping agents that
12
- # don't exist in the current swarm/workflow and returning warnings in RestoreResult.
13
- #
14
- # ## System Prompt Handling
15
- #
16
- # By default, system prompts are taken from the **current YAML configuration**,
17
- # not from the snapshot. This makes configuration the source of truth and allows
18
- # you to update system prompts without creating new sessions.
19
- #
20
- # Set `preserve_system_prompts: true` to use historical prompts from the snapshot
21
- # (useful for debugging, auditing, or exact reproducibility).
22
- #
23
- # @example Restore with current system prompts (default)
24
- # swarm = SwarmSDK.build { ... }
25
- # snapshot_data = JSON.parse(File.read("session.json"), symbolize_names: true)
26
- # result = swarm.restore(snapshot_data)
27
- # # Uses system prompts from current YAML config
28
- #
29
- # @example Restore with historical system prompts
30
- # result = swarm.restore(snapshot_data, preserve_system_prompts: true)
31
- # # Uses system prompts that were active when snapshot was created
32
- class StateRestorer
33
- # Initialize state restorer
34
- #
35
- # @param orchestration [Swarm, Workflow] Swarm or workflow to restore into
36
- # @param snapshot [Snapshot, Hash, String] Snapshot object, hash, or JSON string
37
- # @param preserve_system_prompts [Boolean] If true, use system prompts from snapshot instead of current config (default: false)
38
- def initialize(orchestration, snapshot, preserve_system_prompts: false)
39
- @orchestration = orchestration
40
- @preserve_system_prompts = preserve_system_prompts
41
-
42
- # Handle different input types
43
- @snapshot_data = case snapshot
44
- when Snapshot
45
- snapshot.to_hash
46
- when String
47
- JSON.parse(snapshot, symbolize_names: true)
48
- when Hash
49
- snapshot
50
- else
51
- raise ArgumentError, "snapshot must be a Snapshot object, Hash, or JSON string"
52
- end
53
-
54
- validate_version!
55
- validate_type_match!
56
- end
57
-
58
- # Restore state from snapshot
59
- #
60
- # Three-phase process:
61
- # 1. Validate compatibility (which agents can be restored)
62
- # 2. Restore state (only for matched agents)
63
- # 3. Return result with warnings about skipped agents
64
- #
65
- # @return [RestoreResult] Result with warnings about partial restores
66
- def restore
67
- # Phase 1: Validate compatibility
68
- validation = validate_compatibility
69
-
70
- # Phase 2: Restore state (only for matched agents)
71
- restore_metadata
72
- restore_agent_conversations(validation.restorable_agents)
73
- restore_delegation_conversations(validation.restorable_delegations)
74
- restore_scratchpad
75
- restore_read_tracking
76
- restore_plugin_states
77
-
78
- # Phase 3: Return result with warnings
79
- SwarmSDK::RestoreResult.new(
80
- warnings: validation.warnings,
81
- skipped_agents: validation.skipped_agents,
82
- skipped_delegations: validation.skipped_delegations,
83
- )
84
- end
85
-
86
- private
87
-
88
- # Validate snapshot version
89
- #
90
- # @raise [StateError] if version is unsupported
91
- def validate_version!
92
- version = @snapshot_data[:version] || @snapshot_data["version"]
93
- unless version == "2.1.0"
94
- raise StateError, "Unsupported snapshot version: #{version}. Expected: 2.1.0"
95
- end
96
- end
97
-
98
- # Validate snapshot type matches orchestration type
99
- #
100
- # @raise [StateError] if types don't match
101
- def validate_type_match!
102
- snapshot_type = (@snapshot_data[:type] || @snapshot_data["type"]).to_s.downcase
103
- actual_type = @orchestration.class.name.split("::").last.downcase
104
-
105
- unless snapshot_type == actual_type
106
- raise StateError, "Snapshot type '#{snapshot_type}' doesn't match orchestration type '#{actual_type}'"
107
- end
108
- end
109
-
110
- # Validate compatibility between snapshot and current configuration
111
- #
112
- # @return [ValidationResult] Validation results
113
- def validate_compatibility
114
- warnings = []
115
- skipped_agents = []
116
- restorable_agents = []
117
- skipped_delegations = []
118
- restorable_delegations = []
119
-
120
- # Get current agent names from configuration
121
- current_agents = Set.new(@orchestration.agent_definitions.keys)
122
-
123
- # Check each snapshot agent
124
- snapshot_agents = @snapshot_data[:agents] || @snapshot_data["agents"]
125
- snapshot_agents.keys.each do |agent_name|
126
- agent_name_sym = agent_name.to_sym
127
-
128
- if current_agents.include?(agent_name_sym)
129
- restorable_agents << agent_name_sym
130
- else
131
- skipped_agents << agent_name_sym
132
- warnings << {
133
- type: :agent_not_found,
134
- agent: agent_name,
135
- message: "Agent '#{agent_name}' in snapshot not found in current configuration. " \
136
- "Conversation will not be restored.",
137
- }
138
- end
139
- end
140
-
141
- # Check delegation instances
142
- delegation_instances = @snapshot_data[:delegation_instances] || @snapshot_data["delegation_instances"]
143
- delegation_instances&.each do |instance_name, _data|
144
- base_name, delegator_name = instance_name.split("@")
145
-
146
- # Delegation can be restored if both agents exist in current configuration
147
- if current_agents.include?(base_name.to_sym) &&
148
- restorable_agents.include?(delegator_name.to_sym)
149
- restorable_delegations << instance_name
150
- else
151
- skipped_delegations << instance_name
152
- warnings << {
153
- type: :delegation_instance_not_restorable,
154
- instance: instance_name,
155
- message: "Delegation instance '#{instance_name}' cannot be restored " \
156
- "(base agent or delegator not in current swarm/workflow).",
157
- }
158
- end
159
- end
160
-
161
- SwarmSDK::ValidationResult.new(
162
- warnings: warnings,
163
- skipped_agents: skipped_agents,
164
- restorable_agents: restorable_agents,
165
- skipped_delegations: skipped_delegations,
166
- restorable_delegations: restorable_delegations,
167
- )
168
- end
169
-
170
- # Restore orchestration metadata
171
- #
172
- # @return [void]
173
- def restore_metadata
174
- # Restore metadata
175
- metadata = @snapshot_data[:metadata] || @snapshot_data["metadata"]
176
- return unless metadata
177
-
178
- # Restore first_message_sent flag (Swarm only, no-op for Workflow)
179
- if @orchestration.respond_to?(:first_message_sent=)
180
- first_sent = metadata[:first_message_sent] || metadata["first_message_sent"]
181
- @orchestration.first_message_sent = first_sent
182
- end
183
- end
184
-
185
- # Restore agent conversations
186
- #
187
- # Uses interface methods - no type checking!
188
- #
189
- # @param restorable_agents [Array<Symbol>] Agents that can be restored
190
- # @return [void]
191
- def restore_agent_conversations(restorable_agents)
192
- restorable_agents.each do |agent_name|
193
- # For Swarm: lazy initialization triggers when we call agent()
194
- # For Workflow: agents are in cache if already used, otherwise skip
195
- agent_chat = if @orchestration.is_a?(Swarm)
196
- @orchestration.agent(agent_name)
197
- else
198
- # Workflow: only restore if agent is already in cache
199
- @orchestration.primary_agents[agent_name]
200
- end
201
-
202
- next unless agent_chat
203
-
204
- # Get agent snapshot data
205
- agents_data = @snapshot_data[:agents] || @snapshot_data["agents"]
206
- snapshot_data = agents_data[agent_name] || agents_data[agent_name.to_s]
207
- next unless snapshot_data
208
-
209
- # Restore conversation
210
- restore_agent_conversation(agent_chat, agent_name, snapshot_data)
211
- end
212
- end
213
-
214
- # Restore a single agent's conversation
215
- #
216
- # @param agent_chat [Agent::Chat] Chat instance
217
- # @param agent_name [Symbol] Agent name
218
- # @param snapshot_data [Hash] Snapshot data for this agent
219
- # @return [void]
220
- def restore_agent_conversation(agent_chat, agent_name, snapshot_data)
221
- # Determine which system prompt to use
222
- system_prompt = if @preserve_system_prompts
223
- snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
224
- else
225
- agent_definition = @orchestration.agent_definitions[agent_name]
226
- agent_definition&.system_prompt
227
- end
228
-
229
- # Build complete message list including system message
230
- all_messages = []
231
-
232
- # Add system message first if we have a system prompt
233
- if system_prompt
234
- all_messages << RubyLLM::Message.new(role: :system, content: system_prompt)
235
- end
236
-
237
- # Add conversation messages
238
- conversation = snapshot_data[:conversation] || snapshot_data["conversation"]
239
- restored_messages = conversation.map { |msg_data| deserialize_message(msg_data) }
240
- all_messages.concat(restored_messages)
241
-
242
- # Replace all messages using proper abstraction
243
- agent_chat.replace_messages(all_messages)
244
-
245
- # Restore context state
246
- context_state = snapshot_data[:context_state] || snapshot_data["context_state"]
247
- restore_context_state(agent_chat, context_state)
248
- end
249
-
250
- # Deserialize a message from snapshot data
251
- #
252
- # @param msg_data [Hash] Message data from snapshot
253
- # @return [RubyLLM::Message] Deserialized message
254
- def deserialize_message(msg_data)
255
- # Handle Content objects
256
- content = if msg_data[:content].is_a?(Hash) && (msg_data[:content].key?(:text) || msg_data[:content].key?("text"))
257
- content_data = msg_data[:content]
258
- text = content_data[:text] || content_data["text"]
259
- attachments = content_data[:attachments] || content_data["attachments"] || []
260
-
261
- RubyLLM::Content.new(text, attachments)
262
- else
263
- msg_data[:content]
264
- end
265
-
266
- # Handle tool calls
267
- tool_calls_hash = if msg_data[:tool_calls] && !msg_data[:tool_calls].empty?
268
- msg_data[:tool_calls].each_with_object({}) do |tc_data, hash|
269
- id = tc_data[:id] || tc_data["id"]
270
- name = tc_data[:name] || tc_data["name"]
271
- arguments = tc_data[:arguments] || tc_data["arguments"] || {}
272
-
273
- hash[id.to_s] = RubyLLM::ToolCall.new(
274
- id: id,
275
- name: name,
276
- arguments: arguments,
277
- )
278
- end
279
- end
280
-
281
- RubyLLM::Message.new(
282
- role: (msg_data[:role] || msg_data["role"]).to_sym,
283
- content: content,
284
- tool_calls: tool_calls_hash,
285
- tool_call_id: msg_data[:tool_call_id] || msg_data["tool_call_id"],
286
- input_tokens: msg_data[:input_tokens] || msg_data["input_tokens"],
287
- output_tokens: msg_data[:output_tokens] || msg_data["output_tokens"],
288
- model_id: msg_data[:model_id] || msg_data["model_id"],
289
- )
290
- end
291
-
292
- # Restore context state for an agent
293
- #
294
- # @param agent_chat [Agent::Chat] Agent chat instance
295
- # @param context_state [Hash] Context state data
296
- # @return [void]
297
- def restore_context_state(agent_chat, context_state)
298
- context_manager = agent_chat.context_manager
299
- agent_context = agent_chat.agent_context
300
-
301
- # Restore warning thresholds
302
- if context_state[:warning_thresholds_hit] || context_state["warning_thresholds_hit"]
303
- thresholds_array = context_state[:warning_thresholds_hit] || context_state["warning_thresholds_hit"]
304
- thresholds_set = agent_context.warning_thresholds_hit
305
- thresholds_array.each { |t| thresholds_set.add(t) }
306
- end
307
-
308
- # Restore compression flag
309
- compression = context_state[:compression_applied] || context_state["compression_applied"]
310
- context_manager.compression_applied = compression
311
-
312
- # Restore TodoWrite tracking
313
- todowrite_index = context_state[:last_todowrite_message_index] || context_state["last_todowrite_message_index"]
314
- agent_chat.last_todowrite_message_index = todowrite_index
315
-
316
- # Restore active skill path
317
- skill_path = context_state[:active_skill_path] || context_state["active_skill_path"]
318
- agent_chat.active_skill_path = skill_path
319
- end
320
-
321
- # Restore delegation instance conversations
322
- #
323
- # Uses interface methods - no type checking!
324
- #
325
- # @param restorable_delegations [Array<String>] Delegation instances that can be restored
326
- # @return [void]
327
- def restore_delegation_conversations(restorable_delegations)
328
- restorable_delegations.each do |instance_name|
329
- # Use interface method - works for both!
330
- delegation_chat = @orchestration.delegation_instances_hash[instance_name]
331
- next unless delegation_chat
332
-
333
- # Get delegation snapshot data
334
- delegations_data = @snapshot_data[:delegation_instances] || @snapshot_data["delegation_instances"]
335
- snapshot_data = delegations_data[instance_name.to_sym] || delegations_data[instance_name.to_s] || delegations_data[instance_name]
336
- next unless snapshot_data
337
-
338
- # Extract base agent name
339
- base_name = instance_name.to_s.split("@").first.to_sym
340
-
341
- # Restore conversation
342
- restore_delegation_conversation(delegation_chat, base_name, snapshot_data)
343
- end
344
- end
345
-
346
- # Restore a single delegation's conversation
347
- #
348
- # @param delegation_chat [Agent::Chat] Chat instance
349
- # @param base_name [Symbol] Base agent name
350
- # @param snapshot_data [Hash] Snapshot data
351
- # @return [void]
352
- def restore_delegation_conversation(delegation_chat, base_name, snapshot_data)
353
- # Determine which system prompt to use
354
- system_prompt = if @preserve_system_prompts
355
- snapshot_data[:system_prompt] || snapshot_data["system_prompt"]
356
- else
357
- agent_definition = @orchestration.agent_definitions[base_name]
358
- agent_definition&.system_prompt
359
- end
360
-
361
- # Build complete message list including system message
362
- all_messages = []
363
-
364
- # Add system message first if we have a system prompt
365
- if system_prompt
366
- all_messages << RubyLLM::Message.new(role: :system, content: system_prompt)
367
- end
368
-
369
- # Restore conversation messages
370
- conversation = snapshot_data[:conversation] || snapshot_data["conversation"]
371
- restored_messages = conversation.map { |msg_data| deserialize_message(msg_data) }
372
- all_messages.concat(restored_messages)
373
-
374
- # Replace all messages using proper abstraction
375
- delegation_chat.replace_messages(all_messages)
376
-
377
- # Restore context state
378
- context_state = snapshot_data[:context_state] || snapshot_data["context_state"]
379
- restore_context_state(delegation_chat, context_state)
380
- end
381
-
382
- # Restore scratchpad contents
383
- #
384
- # @return [void]
385
- def restore_scratchpad
386
- scratchpad_data = @snapshot_data[:scratchpad] || @snapshot_data["scratchpad"]
387
- return unless scratchpad_data&.any?
388
-
389
- if @orchestration.is_a?(Workflow)
390
- restore_workflow_scratchpad(scratchpad_data)
391
- else
392
- restore_swarm_scratchpad(scratchpad_data)
393
- end
394
- end
395
-
396
- # Restore scratchpad for Workflow
397
- #
398
- # @param scratchpad_data [Hash] { shared: bool, data: ... }
399
- # @return [void]
400
- def restore_workflow_scratchpad(scratchpad_data)
401
- snapshot_shared_mode = scratchpad_data[:shared] || scratchpad_data["shared"]
402
- data = scratchpad_data[:data] || scratchpad_data["data"]
403
-
404
- return unless data&.any?
405
-
406
- # Warn if snapshot mode doesn't match current configuration
407
- if snapshot_shared_mode != @orchestration.shared_scratchpad?
408
- RubyLLM.logger.warn(
409
- "SwarmSDK: Scratchpad mode mismatch: snapshot=#{snapshot_shared_mode ? "enabled" : "per_node"}, " \
410
- "current=#{@orchestration.shared_scratchpad? ? "enabled" : "per_node"}",
411
- )
412
- RubyLLM.logger.warn("SwarmSDK: Restoring anyway - data may not behave as expected")
413
- end
414
-
415
- if snapshot_shared_mode
416
- # Restore shared scratchpad
417
- shared_scratchpad = @orchestration.scratchpad_for(@orchestration.start_node)
418
- shared_scratchpad&.restore_entries(data)
419
- else
420
- # Restore per-node scratchpads
421
- data.each do |node_name, entries|
422
- next unless entries&.any?
423
-
424
- scratchpad = @orchestration.scratchpad_for(node_name.to_sym)
425
- scratchpad&.restore_entries(entries)
426
- end
427
- end
428
- end
429
-
430
- # Restore scratchpad for Swarm
431
- #
432
- # @param scratchpad_data [Hash] Flat scratchpad entries
433
- # @return [void]
434
- def restore_swarm_scratchpad(scratchpad_data)
435
- scratchpad = @orchestration.scratchpad_storage
436
- return unless scratchpad
437
-
438
- scratchpad.restore_entries(scratchpad_data)
439
- end
440
-
441
- # Restore read tracking state
442
- #
443
- # @return [void]
444
- def restore_read_tracking
445
- read_tracking_data = @snapshot_data[:read_tracking] || @snapshot_data["read_tracking"]
446
- return unless read_tracking_data
447
-
448
- read_tracking_data.each do |agent_name, files_with_digests|
449
- agent_sym = agent_name.to_sym
450
- Tools::Stores::ReadTracker.restore_read_files(agent_sym, files_with_digests)
451
- end
452
- end
453
-
454
- # Restore plugin-specific state for all plugins
455
- #
456
- # @return [void]
457
- def restore_plugin_states
458
- plugin_states_data = @snapshot_data[:plugin_states] || @snapshot_data["plugin_states"]
459
- return unless plugin_states_data
460
-
461
- plugin_states_data.each do |plugin_name, agents_state|
462
- # Find plugin by name
463
- plugin = PluginRegistry.all.find { |p| p.name.to_s == plugin_name.to_s }
464
- next unless plugin
465
-
466
- # Restore state for each agent
467
- agents_state.each do |agent_name, state|
468
- agent_sym = agent_name.to_sym
469
- # Symbolize keys for consistent access
470
- symbolized_state = state.is_a?(Hash) ? state.transform_keys(&:to_sym) : state
471
- plugin.restore_agent_state(agent_sym, symbolized_state)
472
- end
473
- end
474
- end
475
- end
476
- end