@elizaos/python 2.0.0-alpha.10

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 (197) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +239 -0
  3. package/elizaos/__init__.py +280 -0
  4. package/elizaos/action_docs.py +149 -0
  5. package/elizaos/advanced_capabilities/__init__.py +85 -0
  6. package/elizaos/advanced_capabilities/actions/__init__.py +54 -0
  7. package/elizaos/advanced_capabilities/actions/add_contact.py +139 -0
  8. package/elizaos/advanced_capabilities/actions/follow_room.py +151 -0
  9. package/elizaos/advanced_capabilities/actions/image_generation.py +148 -0
  10. package/elizaos/advanced_capabilities/actions/mute_room.py +164 -0
  11. package/elizaos/advanced_capabilities/actions/remove_contact.py +145 -0
  12. package/elizaos/advanced_capabilities/actions/roles.py +207 -0
  13. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +154 -0
  14. package/elizaos/advanced_capabilities/actions/search_contacts.py +145 -0
  15. package/elizaos/advanced_capabilities/actions/send_message.py +187 -0
  16. package/elizaos/advanced_capabilities/actions/settings.py +151 -0
  17. package/elizaos/advanced_capabilities/actions/unfollow_room.py +164 -0
  18. package/elizaos/advanced_capabilities/actions/unmute_room.py +164 -0
  19. package/elizaos/advanced_capabilities/actions/update_contact.py +164 -0
  20. package/elizaos/advanced_capabilities/actions/update_entity.py +161 -0
  21. package/elizaos/advanced_capabilities/evaluators/__init__.py +18 -0
  22. package/elizaos/advanced_capabilities/evaluators/reflection.py +134 -0
  23. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +203 -0
  24. package/elizaos/advanced_capabilities/providers/__init__.py +36 -0
  25. package/elizaos/advanced_capabilities/providers/agent_settings.py +60 -0
  26. package/elizaos/advanced_capabilities/providers/contacts.py +77 -0
  27. package/elizaos/advanced_capabilities/providers/facts.py +82 -0
  28. package/elizaos/advanced_capabilities/providers/follow_ups.py +113 -0
  29. package/elizaos/advanced_capabilities/providers/knowledge.py +83 -0
  30. package/elizaos/advanced_capabilities/providers/relationships.py +112 -0
  31. package/elizaos/advanced_capabilities/providers/roles.py +97 -0
  32. package/elizaos/advanced_capabilities/providers/settings.py +51 -0
  33. package/elizaos/advanced_capabilities/services/__init__.py +18 -0
  34. package/elizaos/advanced_capabilities/services/follow_up.py +138 -0
  35. package/elizaos/advanced_capabilities/services/rolodex.py +244 -0
  36. package/elizaos/advanced_memory/__init__.py +3 -0
  37. package/elizaos/advanced_memory/evaluators.py +97 -0
  38. package/elizaos/advanced_memory/memory_service.py +556 -0
  39. package/elizaos/advanced_memory/plugin.py +30 -0
  40. package/elizaos/advanced_memory/prompts.py +12 -0
  41. package/elizaos/advanced_memory/providers.py +90 -0
  42. package/elizaos/advanced_memory/types.py +65 -0
  43. package/elizaos/advanced_planning/__init__.py +10 -0
  44. package/elizaos/advanced_planning/actions.py +145 -0
  45. package/elizaos/advanced_planning/message_classifier.py +127 -0
  46. package/elizaos/advanced_planning/planning_service.py +712 -0
  47. package/elizaos/advanced_planning/plugin.py +40 -0
  48. package/elizaos/advanced_planning/prompts.py +4 -0
  49. package/elizaos/basic_capabilities/__init__.py +66 -0
  50. package/elizaos/basic_capabilities/actions/__init__.py +24 -0
  51. package/elizaos/basic_capabilities/actions/choice.py +140 -0
  52. package/elizaos/basic_capabilities/actions/ignore.py +66 -0
  53. package/elizaos/basic_capabilities/actions/none.py +56 -0
  54. package/elizaos/basic_capabilities/actions/reply.py +120 -0
  55. package/elizaos/basic_capabilities/providers/__init__.py +54 -0
  56. package/elizaos/basic_capabilities/providers/action_state.py +113 -0
  57. package/elizaos/basic_capabilities/providers/actions.py +263 -0
  58. package/elizaos/basic_capabilities/providers/attachments.py +76 -0
  59. package/elizaos/basic_capabilities/providers/capabilities.py +62 -0
  60. package/elizaos/basic_capabilities/providers/character.py +113 -0
  61. package/elizaos/basic_capabilities/providers/choice.py +73 -0
  62. package/elizaos/basic_capabilities/providers/context_bench.py +44 -0
  63. package/elizaos/basic_capabilities/providers/current_time.py +58 -0
  64. package/elizaos/basic_capabilities/providers/entities.py +99 -0
  65. package/elizaos/basic_capabilities/providers/evaluators.py +54 -0
  66. package/elizaos/basic_capabilities/providers/providers_list.py +55 -0
  67. package/elizaos/basic_capabilities/providers/recent_messages.py +85 -0
  68. package/elizaos/basic_capabilities/providers/time.py +45 -0
  69. package/elizaos/basic_capabilities/providers/world.py +93 -0
  70. package/elizaos/basic_capabilities/services/__init__.py +18 -0
  71. package/elizaos/basic_capabilities/services/embedding.py +122 -0
  72. package/elizaos/basic_capabilities/services/task.py +178 -0
  73. package/elizaos/bootstrap/__init__.py +12 -0
  74. package/elizaos/bootstrap/actions/__init__.py +68 -0
  75. package/elizaos/bootstrap/actions/add_contact.py +149 -0
  76. package/elizaos/bootstrap/actions/choice.py +147 -0
  77. package/elizaos/bootstrap/actions/follow_room.py +151 -0
  78. package/elizaos/bootstrap/actions/ignore.py +80 -0
  79. package/elizaos/bootstrap/actions/image_generation.py +135 -0
  80. package/elizaos/bootstrap/actions/mute_room.py +151 -0
  81. package/elizaos/bootstrap/actions/none.py +71 -0
  82. package/elizaos/bootstrap/actions/remove_contact.py +159 -0
  83. package/elizaos/bootstrap/actions/reply.py +140 -0
  84. package/elizaos/bootstrap/actions/roles.py +193 -0
  85. package/elizaos/bootstrap/actions/schedule_follow_up.py +164 -0
  86. package/elizaos/bootstrap/actions/search_contacts.py +159 -0
  87. package/elizaos/bootstrap/actions/send_message.py +173 -0
  88. package/elizaos/bootstrap/actions/settings.py +165 -0
  89. package/elizaos/bootstrap/actions/unfollow_room.py +151 -0
  90. package/elizaos/bootstrap/actions/unmute_room.py +151 -0
  91. package/elizaos/bootstrap/actions/update_contact.py +178 -0
  92. package/elizaos/bootstrap/actions/update_entity.py +175 -0
  93. package/elizaos/bootstrap/autonomy/__init__.py +18 -0
  94. package/elizaos/bootstrap/autonomy/action.py +197 -0
  95. package/elizaos/bootstrap/autonomy/providers.py +165 -0
  96. package/elizaos/bootstrap/autonomy/routes.py +171 -0
  97. package/elizaos/bootstrap/autonomy/service.py +562 -0
  98. package/elizaos/bootstrap/autonomy/types.py +18 -0
  99. package/elizaos/bootstrap/evaluators/__init__.py +19 -0
  100. package/elizaos/bootstrap/evaluators/reflection.py +118 -0
  101. package/elizaos/bootstrap/evaluators/relationship_extraction.py +192 -0
  102. package/elizaos/bootstrap/plugin.py +140 -0
  103. package/elizaos/bootstrap/providers/__init__.py +80 -0
  104. package/elizaos/bootstrap/providers/action_state.py +71 -0
  105. package/elizaos/bootstrap/providers/actions.py +256 -0
  106. package/elizaos/bootstrap/providers/agent_settings.py +63 -0
  107. package/elizaos/bootstrap/providers/attachments.py +76 -0
  108. package/elizaos/bootstrap/providers/capabilities.py +66 -0
  109. package/elizaos/bootstrap/providers/character.py +128 -0
  110. package/elizaos/bootstrap/providers/choice.py +77 -0
  111. package/elizaos/bootstrap/providers/contacts.py +78 -0
  112. package/elizaos/bootstrap/providers/context_bench.py +49 -0
  113. package/elizaos/bootstrap/providers/current_time.py +56 -0
  114. package/elizaos/bootstrap/providers/entities.py +99 -0
  115. package/elizaos/bootstrap/providers/evaluators.py +58 -0
  116. package/elizaos/bootstrap/providers/facts.py +86 -0
  117. package/elizaos/bootstrap/providers/follow_ups.py +116 -0
  118. package/elizaos/bootstrap/providers/knowledge.py +73 -0
  119. package/elizaos/bootstrap/providers/providers_list.py +59 -0
  120. package/elizaos/bootstrap/providers/recent_messages.py +85 -0
  121. package/elizaos/bootstrap/providers/relationships.py +106 -0
  122. package/elizaos/bootstrap/providers/roles.py +95 -0
  123. package/elizaos/bootstrap/providers/settings.py +55 -0
  124. package/elizaos/bootstrap/providers/time.py +45 -0
  125. package/elizaos/bootstrap/providers/world.py +97 -0
  126. package/elizaos/bootstrap/services/__init__.py +26 -0
  127. package/elizaos/bootstrap/services/embedding.py +122 -0
  128. package/elizaos/bootstrap/services/follow_up.py +138 -0
  129. package/elizaos/bootstrap/services/rolodex.py +244 -0
  130. package/elizaos/bootstrap/services/task.py +585 -0
  131. package/elizaos/bootstrap/types.py +54 -0
  132. package/elizaos/bootstrap/utils/__init__.py +7 -0
  133. package/elizaos/bootstrap/utils/xml.py +69 -0
  134. package/elizaos/character.py +149 -0
  135. package/elizaos/logger.py +179 -0
  136. package/elizaos/media/__init__.py +45 -0
  137. package/elizaos/media/mime.py +315 -0
  138. package/elizaos/media/search.py +161 -0
  139. package/elizaos/media/tests/__init__.py +1 -0
  140. package/elizaos/media/tests/test_mime.py +117 -0
  141. package/elizaos/media/tests/test_search.py +156 -0
  142. package/elizaos/plugin.py +191 -0
  143. package/elizaos/prompts.py +1071 -0
  144. package/elizaos/py.typed +0 -0
  145. package/elizaos/runtime.py +2572 -0
  146. package/elizaos/services/__init__.py +49 -0
  147. package/elizaos/services/hook_service.py +511 -0
  148. package/elizaos/services/message_service.py +1248 -0
  149. package/elizaos/settings.py +182 -0
  150. package/elizaos/streaming_context.py +159 -0
  151. package/elizaos/trajectory_context.py +18 -0
  152. package/elizaos/types/__init__.py +512 -0
  153. package/elizaos/types/agent.py +31 -0
  154. package/elizaos/types/components.py +208 -0
  155. package/elizaos/types/database.py +64 -0
  156. package/elizaos/types/environment.py +46 -0
  157. package/elizaos/types/events.py +47 -0
  158. package/elizaos/types/memory.py +45 -0
  159. package/elizaos/types/model.py +393 -0
  160. package/elizaos/types/plugin.py +188 -0
  161. package/elizaos/types/primitives.py +100 -0
  162. package/elizaos/types/runtime.py +460 -0
  163. package/elizaos/types/service.py +113 -0
  164. package/elizaos/types/service_interfaces.py +244 -0
  165. package/elizaos/types/state.py +188 -0
  166. package/elizaos/types/task.py +29 -0
  167. package/elizaos/utils/__init__.py +108 -0
  168. package/elizaos/utils/spec_examples.py +48 -0
  169. package/elizaos/utils/streaming.py +426 -0
  170. package/elizaos_atropos_shared/__init__.py +1 -0
  171. package/elizaos_atropos_shared/canonical_eliza.py +282 -0
  172. package/package.json +19 -0
  173. package/pyproject.toml +143 -0
  174. package/requirements-dev.in +11 -0
  175. package/requirements-dev.lock +134 -0
  176. package/requirements.in +9 -0
  177. package/requirements.lock +64 -0
  178. package/tests/__init__.py +0 -0
  179. package/tests/test_action_parameters.py +154 -0
  180. package/tests/test_actions_provider_examples.py +39 -0
  181. package/tests/test_advanced_memory_behavior.py +96 -0
  182. package/tests/test_advanced_memory_flag.py +30 -0
  183. package/tests/test_advanced_planning_behavior.py +225 -0
  184. package/tests/test_advanced_planning_flag.py +26 -0
  185. package/tests/test_autonomy.py +445 -0
  186. package/tests/test_bootstrap_initialize.py +37 -0
  187. package/tests/test_character.py +163 -0
  188. package/tests/test_character_provider.py +231 -0
  189. package/tests/test_dynamic_prompt_exec.py +561 -0
  190. package/tests/test_logger_redaction.py +43 -0
  191. package/tests/test_plugin.py +117 -0
  192. package/tests/test_runtime.py +422 -0
  193. package/tests/test_salt_production_enforcement.py +22 -0
  194. package/tests/test_settings_crypto.py +118 -0
  195. package/tests/test_streaming.py +295 -0
  196. package/tests/test_types.py +221 -0
  197. package/tests/test_uuid_parity.py +46 -0
@@ -0,0 +1,156 @@
1
+ """Tests for hybrid search utilities."""
2
+
3
+ from elizaos.media.search import (
4
+ HybridKeywordResult,
5
+ HybridVectorResult,
6
+ bm25_rank_to_score,
7
+ build_fts_query,
8
+ merge_hybrid_results,
9
+ )
10
+
11
+
12
+ class TestBuildFtsQuery:
13
+ def test_simple_query(self):
14
+ result = build_fts_query("hello world")
15
+ assert result == '"hello" AND "world"'
16
+
17
+ def test_single_token(self):
18
+ result = build_fts_query("test_function")
19
+ assert result == '"test_function"'
20
+
21
+ def test_empty_string(self):
22
+ assert build_fts_query("") is None
23
+
24
+ def test_whitespace_only(self):
25
+ assert build_fts_query(" ") is None
26
+
27
+ def test_special_characters(self):
28
+ result = build_fts_query("hello! world?")
29
+ assert result == '"hello" AND "world"'
30
+
31
+
32
+ class TestBm25RankToScore:
33
+ def test_zero_rank(self):
34
+ score = bm25_rank_to_score(0.0)
35
+ assert abs(score - 1.0) < 0.001
36
+
37
+ def test_one_rank(self):
38
+ score = bm25_rank_to_score(1.0)
39
+ assert abs(score - 0.5) < 0.001
40
+
41
+ def test_infinite_rank(self):
42
+ score = bm25_rank_to_score(float("inf"))
43
+ assert score < 0.01
44
+
45
+ def test_negative_rank(self):
46
+ # Negative ranks should be clamped to 0
47
+ score = bm25_rank_to_score(-5.0)
48
+ assert abs(score - 1.0) < 0.001
49
+
50
+
51
+ class TestMergeHybridResults:
52
+ def test_merge_single_result(self):
53
+ vector = [
54
+ HybridVectorResult(
55
+ id="a",
56
+ path="file.py",
57
+ start_line=1,
58
+ end_line=10,
59
+ source="test",
60
+ snippet="hello",
61
+ vector_score=0.8,
62
+ )
63
+ ]
64
+
65
+ keyword = [
66
+ HybridKeywordResult(
67
+ id="a",
68
+ path="file.py",
69
+ start_line=1,
70
+ end_line=10,
71
+ source="test",
72
+ snippet="hello world",
73
+ text_score=0.6,
74
+ )
75
+ ]
76
+
77
+ merged = merge_hybrid_results(vector, keyword, 0.7, 0.3)
78
+
79
+ assert len(merged) == 1
80
+ expected_score = 0.7 * 0.8 + 0.3 * 0.6
81
+ assert abs(merged[0].score - expected_score) < 0.001
82
+ # Keyword snippet should be preferred
83
+ assert merged[0].snippet == "hello world"
84
+
85
+ def test_merge_disjoint_results(self):
86
+ vector = [
87
+ HybridVectorResult(
88
+ id="a",
89
+ path="file1.py",
90
+ start_line=1,
91
+ end_line=10,
92
+ source="test",
93
+ snippet="first",
94
+ vector_score=0.9,
95
+ )
96
+ ]
97
+
98
+ keyword = [
99
+ HybridKeywordResult(
100
+ id="b",
101
+ path="file2.py",
102
+ start_line=20,
103
+ end_line=30,
104
+ source="test",
105
+ snippet="second",
106
+ text_score=0.7,
107
+ )
108
+ ]
109
+
110
+ merged = merge_hybrid_results(vector, keyword, 0.7, 0.3)
111
+
112
+ assert len(merged) == 2
113
+ # Results should be sorted by score descending
114
+ assert merged[0].score >= merged[1].score
115
+
116
+ def test_empty_inputs(self):
117
+ merged = merge_hybrid_results([], [], 0.7, 0.3)
118
+ assert len(merged) == 0
119
+
120
+ def test_vector_only(self):
121
+ vector = [
122
+ HybridVectorResult(
123
+ id="a",
124
+ path="file.py",
125
+ start_line=1,
126
+ end_line=10,
127
+ source="test",
128
+ snippet="hello",
129
+ vector_score=0.8,
130
+ )
131
+ ]
132
+
133
+ merged = merge_hybrid_results(vector, [], 0.7, 0.3)
134
+
135
+ assert len(merged) == 1
136
+ expected_score = 0.7 * 0.8
137
+ assert abs(merged[0].score - expected_score) < 0.001
138
+
139
+ def test_keyword_only(self):
140
+ keyword = [
141
+ HybridKeywordResult(
142
+ id="a",
143
+ path="file.py",
144
+ start_line=1,
145
+ end_line=10,
146
+ source="test",
147
+ snippet="hello",
148
+ text_score=0.6,
149
+ )
150
+ ]
151
+
152
+ merged = merge_hybrid_results([], keyword, 0.7, 0.3)
153
+
154
+ assert len(merged) == 1
155
+ expected_score = 0.3 * 0.6
156
+ assert abs(merged[0].score - expected_score) < 0.001
@@ -0,0 +1,191 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ from typing import TYPE_CHECKING
5
+
6
+ from elizaos.logger import logger
7
+ from elizaos.types.plugin import Plugin
8
+
9
+ if TYPE_CHECKING:
10
+ from elizaos.types.runtime import IAgentRuntime
11
+
12
+
13
+ class PluginLoadError(Exception):
14
+ def __init__(self, message: str, plugin_name: str, cause: Exception | None = None) -> None:
15
+ super().__init__(message)
16
+ self.plugin_name = plugin_name
17
+ self.cause = cause
18
+
19
+
20
+ class PluginRegistrationError(Exception):
21
+ def __init__(self, message: str, plugin_name: str, cause: Exception | None = None) -> None:
22
+ super().__init__(message)
23
+ self.plugin_name = plugin_name
24
+ self.cause = cause
25
+
26
+
27
+ def load_plugin(name: str) -> Plugin:
28
+ try:
29
+ module = importlib.import_module(name)
30
+ if hasattr(module, "plugin"):
31
+ plugin = module.plugin
32
+ if isinstance(plugin, Plugin):
33
+ logger.debug(f"Loaded plugin: {plugin.name}")
34
+ return plugin
35
+ if isinstance(plugin, dict):
36
+ return Plugin(**plugin)
37
+ if hasattr(module, "default"):
38
+ default = module.default
39
+ if isinstance(default, Plugin):
40
+ logger.debug(f"Loaded plugin from default export: {default.name}")
41
+ return default
42
+ if isinstance(default, dict):
43
+ return Plugin(**default)
44
+
45
+ raise PluginLoadError(
46
+ f"Module {name} does not export a valid plugin",
47
+ plugin_name=name,
48
+ )
49
+
50
+ except ImportError as e:
51
+ raise PluginLoadError(
52
+ f"Failed to import plugin module: {name}",
53
+ plugin_name=name,
54
+ cause=e,
55
+ ) from e
56
+ except Exception as e:
57
+ raise PluginLoadError(
58
+ f"Failed to load plugin {name}: {e}",
59
+ plugin_name=name,
60
+ cause=e,
61
+ ) from e
62
+
63
+
64
+ async def register_plugin(runtime: IAgentRuntime, plugin: Plugin) -> None:
65
+ try:
66
+ logger.info(f"Registering plugin: {plugin.name}")
67
+ # Use getattr for optional plugin attributes to handle non-standard plugins
68
+ dependencies = getattr(plugin, "dependencies", None)
69
+ if dependencies:
70
+ for dep in dependencies:
71
+ if dep not in [p.name for p in runtime.plugins]:
72
+ raise PluginRegistrationError(
73
+ f"Missing dependency: {dep}",
74
+ plugin_name=plugin.name,
75
+ )
76
+
77
+ init_fn = getattr(plugin, "init", None)
78
+ if init_fn:
79
+ config = getattr(plugin, "config", None) or {}
80
+ # Handle different init signatures
81
+ import inspect
82
+
83
+ sig = inspect.signature(init_fn)
84
+ params = list(sig.parameters.values())
85
+ # Filter out 'self' parameter for bound methods
86
+ non_self_params = [p for p in params if p.name != "self"]
87
+ if len(non_self_params) >= 2:
88
+ await init_fn(config, runtime)
89
+ elif len(non_self_params) == 1:
90
+ await init_fn(config)
91
+ else:
92
+ await init_fn()
93
+
94
+ actions = getattr(plugin, "actions", None)
95
+ if actions:
96
+ for action in actions:
97
+ runtime.register_action(action)
98
+ logger.debug(f"Registered action: {action.name}")
99
+
100
+ providers = getattr(plugin, "providers", None)
101
+ if providers:
102
+ for provider in providers:
103
+ runtime.register_provider(provider)
104
+ logger.debug(f"Registered provider: {provider.name}")
105
+
106
+ evaluators = getattr(plugin, "evaluators", None)
107
+ if evaluators:
108
+ for evaluator in evaluators:
109
+ runtime.register_evaluator(evaluator)
110
+ logger.debug(f"Registered evaluator: {evaluator.name}")
111
+
112
+ services = getattr(plugin, "services", None)
113
+ if services:
114
+ for service_class in services:
115
+ await runtime.register_service(service_class)
116
+ logger.debug(
117
+ f"Registered service: {getattr(service_class, 'service_type', 'unknown')}"
118
+ )
119
+
120
+ models = getattr(plugin, "models", None)
121
+ if models:
122
+ for model_type, handler in models.items():
123
+ runtime.register_model(
124
+ model_type,
125
+ handler,
126
+ provider=plugin.name,
127
+ )
128
+ logger.debug(f"Registered model: {model_type}")
129
+
130
+ streaming_models = getattr(plugin, "streaming_models", None)
131
+ if streaming_models:
132
+ for model_type, handler in streaming_models.items():
133
+ runtime.register_streaming_model(
134
+ model_type,
135
+ handler,
136
+ provider=plugin.name,
137
+ )
138
+ logger.debug(f"Registered streaming model: {model_type}")
139
+
140
+ events = getattr(plugin, "events", None)
141
+ if events:
142
+ for event_type, event_handlers in events.items():
143
+ for event_handler in event_handlers:
144
+ runtime.register_event(event_type, event_handler)
145
+ logger.debug(f"Registered event handlers for: {event_type}")
146
+
147
+ logger.info(f"Plugin registered successfully: {plugin.name}")
148
+
149
+ except PluginRegistrationError:
150
+ raise
151
+ except Exception as e:
152
+ raise PluginRegistrationError(
153
+ f"Failed to register plugin {plugin.name}: {e}",
154
+ plugin_name=plugin.name,
155
+ cause=e,
156
+ ) from e
157
+
158
+
159
+ def resolve_plugin_dependencies(plugins: list[Plugin]) -> list[Plugin]:
160
+ plugin_map = {p.name: p for p in plugins}
161
+ resolved: list[Plugin] = []
162
+ visiting: set[str] = set()
163
+ visited: set[str] = set()
164
+
165
+ def visit(name: str) -> None:
166
+ if name in visited:
167
+ return
168
+ if name in visiting:
169
+ raise PluginLoadError(
170
+ f"Circular dependency detected: {name}",
171
+ plugin_name=name,
172
+ )
173
+
174
+ visiting.add(name)
175
+ plugin = plugin_map.get(name)
176
+
177
+ if plugin and plugin.dependencies:
178
+ for dep in plugin.dependencies:
179
+ if dep in plugin_map:
180
+ visit(dep)
181
+
182
+ visiting.remove(name)
183
+ visited.add(name)
184
+
185
+ if plugin:
186
+ resolved.append(plugin)
187
+
188
+ for plugin in plugins:
189
+ visit(plugin.name)
190
+
191
+ return resolved