@elizaos/python 2.0.0-alpha.3 → 2.0.0-alpha.31

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 (167) hide show
  1. package/elizaos/__init__.py +0 -1
  2. package/elizaos/advanced_capabilities/__init__.py +6 -41
  3. package/elizaos/advanced_capabilities/actions/__init__.py +1 -21
  4. package/elizaos/advanced_capabilities/actions/add_contact.py +24 -13
  5. package/elizaos/advanced_capabilities/actions/follow_room.py +29 -29
  6. package/elizaos/advanced_capabilities/actions/image_generation.py +15 -28
  7. package/elizaos/advanced_capabilities/actions/mute_room.py +15 -28
  8. package/elizaos/advanced_capabilities/actions/remove_contact.py +17 -3
  9. package/elizaos/advanced_capabilities/actions/roles.py +17 -30
  10. package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
  11. package/elizaos/advanced_capabilities/actions/search_contacts.py +17 -3
  12. package/elizaos/advanced_capabilities/actions/send_message.py +184 -51
  13. package/elizaos/advanced_capabilities/actions/settings.py +17 -3
  14. package/elizaos/advanced_capabilities/actions/unfollow_room.py +15 -28
  15. package/elizaos/advanced_capabilities/actions/unmute_room.py +15 -28
  16. package/elizaos/advanced_capabilities/actions/update_contact.py +17 -3
  17. package/elizaos/advanced_capabilities/actions/update_entity.py +17 -3
  18. package/elizaos/advanced_capabilities/evaluators/__init__.py +2 -9
  19. package/elizaos/advanced_capabilities/evaluators/reflection.py +3 -132
  20. package/elizaos/advanced_capabilities/evaluators/relationship_extraction.py +5 -201
  21. package/elizaos/advanced_capabilities/providers/__init__.py +1 -12
  22. package/elizaos/advanced_capabilities/providers/knowledge.py +23 -3
  23. package/elizaos/advanced_capabilities/services/__init__.py +2 -9
  24. package/elizaos/advanced_capabilities/services/rolodex.py +2 -2
  25. package/elizaos/advanced_memory/actions/reset_session.py +143 -0
  26. package/elizaos/advanced_memory/evaluators/reflection.py +134 -0
  27. package/elizaos/advanced_memory/evaluators/relationship_extraction.py +203 -0
  28. package/elizaos/advanced_memory/memory_service.py +69 -27
  29. package/elizaos/advanced_memory/plugin.py +2 -1
  30. package/elizaos/advanced_memory/test_advanced_memory.py +357 -0
  31. package/elizaos/advanced_memory/types.py +2 -2
  32. package/elizaos/advanced_planning/actions/schedule_follow_up.py +222 -0
  33. package/elizaos/advanced_planning/planning_service.py +26 -14
  34. package/elizaos/basic_capabilities/__init__.py +0 -2
  35. package/elizaos/basic_capabilities/providers/__init__.py +0 -3
  36. package/elizaos/basic_capabilities/providers/actions.py +118 -29
  37. package/elizaos/basic_capabilities/providers/agent_settings.py +64 -0
  38. package/elizaos/basic_capabilities/providers/character.py +19 -21
  39. package/elizaos/basic_capabilities/providers/contacts.py +79 -0
  40. package/elizaos/basic_capabilities/providers/current_time.py +7 -4
  41. package/elizaos/basic_capabilities/providers/facts.py +87 -0
  42. package/elizaos/basic_capabilities/providers/follow_ups.py +117 -0
  43. package/elizaos/basic_capabilities/providers/knowledge.py +96 -0
  44. package/elizaos/basic_capabilities/providers/recent_messages.py +5 -0
  45. package/elizaos/basic_capabilities/providers/relationships.py +113 -0
  46. package/elizaos/basic_capabilities/providers/roles.py +96 -0
  47. package/elizaos/basic_capabilities/providers/settings.py +56 -0
  48. package/elizaos/basic_capabilities/providers/time.py +7 -4
  49. package/elizaos/basic_capabilities/services/embedding.py +10 -7
  50. package/elizaos/basic_capabilities/services/task.py +3 -3
  51. package/elizaos/bootstrap/__init__.py +21 -2
  52. package/elizaos/bootstrap/actions/__init__.py +3 -0
  53. package/elizaos/bootstrap/actions/reset_session.py +3 -0
  54. package/elizaos/bootstrap/actions/roles.py +5 -4
  55. package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
  56. package/elizaos/bootstrap/actions/send_message.py +162 -15
  57. package/elizaos/bootstrap/autonomy/__init__.py +5 -1
  58. package/elizaos/bootstrap/autonomy/action.py +161 -0
  59. package/elizaos/bootstrap/autonomy/evaluators.py +217 -0
  60. package/elizaos/bootstrap/autonomy/service.py +238 -28
  61. package/elizaos/bootstrap/plugin.py +7 -0
  62. package/elizaos/bootstrap/providers/actions.py +118 -27
  63. package/elizaos/bootstrap/providers/agent_settings.py +1 -0
  64. package/elizaos/bootstrap/providers/attachments.py +1 -0
  65. package/elizaos/bootstrap/providers/capabilities.py +1 -0
  66. package/elizaos/bootstrap/providers/character.py +1 -0
  67. package/elizaos/bootstrap/providers/choice.py +1 -0
  68. package/elizaos/bootstrap/providers/contacts.py +1 -0
  69. package/elizaos/bootstrap/providers/current_time.py +8 -2
  70. package/elizaos/bootstrap/providers/entities.py +1 -0
  71. package/elizaos/bootstrap/providers/evaluators.py +1 -0
  72. package/elizaos/bootstrap/providers/facts.py +1 -0
  73. package/elizaos/bootstrap/providers/follow_ups.py +1 -0
  74. package/elizaos/bootstrap/providers/knowledge.py +26 -3
  75. package/elizaos/bootstrap/providers/providers_list.py +1 -0
  76. package/elizaos/bootstrap/providers/recent_messages.py +5 -0
  77. package/elizaos/bootstrap/providers/relationships.py +20 -13
  78. package/elizaos/bootstrap/providers/roles.py +1 -0
  79. package/elizaos/bootstrap/providers/settings.py +1 -0
  80. package/elizaos/bootstrap/providers/time.py +8 -4
  81. package/elizaos/bootstrap/providers/world.py +1 -0
  82. package/elizaos/bootstrap/services/embedding.py +206 -8
  83. package/elizaos/bootstrap/services/rolodex.py +2 -2
  84. package/elizaos/bootstrap/services/task.py +3 -3
  85. package/elizaos/deterministic.py +193 -0
  86. package/elizaos/generated/__init__.py +1 -0
  87. package/elizaos/generated/action_docs.py +3181 -0
  88. package/elizaos/generated/spec_helpers.py +175 -0
  89. package/elizaos/media/mime.py +4 -4
  90. package/elizaos/media/search.py +23 -23
  91. package/elizaos/runtime.py +223 -64
  92. package/elizaos/services/hook_service.py +3 -3
  93. package/elizaos/services/message_service.py +175 -29
  94. package/elizaos/types/components.py +2 -2
  95. package/elizaos/types/generated/__init__.py +12 -0
  96. package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
  97. package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +159 -0
  98. package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
  99. package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
  100. package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
  101. package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
  102. package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
  103. package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
  104. package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
  105. package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
  106. package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
  107. package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
  108. package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
  109. package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
  110. package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
  111. package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
  112. package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
  113. package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
  114. package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
  115. package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
  116. package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
  117. package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
  118. package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
  119. package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
  120. package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
  121. package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
  122. package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
  123. package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
  124. package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
  125. package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
  126. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
  127. package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
  128. package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
  129. package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
  130. package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
  131. package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
  132. package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
  133. package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
  134. package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
  135. package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
  136. package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
  137. package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
  138. package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
  139. package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
  140. package/elizaos/types/model.py +33 -3
  141. package/elizaos/types/primitives.py +3 -3
  142. package/elizaos/types/runtime.py +17 -3
  143. package/elizaos/types/state.py +2 -2
  144. package/elizaos/utils/streaming.py +3 -3
  145. package/elizaos/utils/validation.py +76 -0
  146. package/package.json +4 -3
  147. package/pyproject.toml +1 -2
  148. package/requirements-dev.lock +2 -2
  149. package/requirements.in +1 -2
  150. package/requirements.lock +2 -2
  151. package/tests/test_action_parameters.py +2 -3
  152. package/tests/test_actions_provider_examples.py +58 -1
  153. package/tests/test_advanced_memory_behavior.py +0 -2
  154. package/tests/test_advanced_memory_flag.py +0 -2
  155. package/tests/test_advanced_planning_behavior.py +11 -5
  156. package/tests/test_async_embedding.py +124 -0
  157. package/tests/test_autonomy.py +24 -3
  158. package/tests/test_history_compaction.py +104 -0
  159. package/tests/test_memory_bounds.py +115 -0
  160. package/tests/test_runtime.py +8 -17
  161. package/tests/test_schedule_follow_up_action.py +260 -0
  162. package/tests/test_send_message_action_targets.py +114 -0
  163. package/tests/test_settings_crypto.py +0 -2
  164. package/tests/test_validation.py +141 -0
  165. package/tests/verify_memory_architecture.py +192 -0
  166. package/uv.lock +1565 -0
  167. package/elizaos/basic_capabilities/providers/capabilities.py +0 -62
@@ -1,12 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
- from enum import Enum
5
- from typing import TYPE_CHECKING
4
+ from enum import StrEnum
5
+ from typing import TYPE_CHECKING, Any, cast
6
6
  from uuid import UUID
7
7
 
8
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
9
  from elizaos.generated.spec_helpers import require_action_spec
10
+ from elizaos.prompts import UPDATE_ROLE_TEMPLATE
10
11
  from elizaos.types import Action, ActionExample, ActionResult, Content, ModelType
11
12
 
12
13
  if TYPE_CHECKING:
@@ -18,7 +19,7 @@ _spec = require_action_spec("UPDATE_ROLE")
18
19
 
19
20
  def _convert_spec_examples() -> list[list[ActionExample]]:
20
21
  """Convert spec examples to ActionExample format."""
21
- spec_examples = _spec.get("examples", [])
22
+ spec_examples = cast(list[list[dict[str, Any]]], _spec.get("examples", []))
22
23
  if spec_examples:
23
24
  return [
24
25
  [
@@ -36,7 +37,7 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
36
37
  return []
37
38
 
38
39
 
39
- class Role(str, Enum):
40
+ class Role(StrEnum):
40
41
  OWNER = "OWNER"
41
42
  ADMIN = "ADMIN"
42
43
  MEMBER = "MEMBER"
@@ -3,8 +3,10 @@ from __future__ import annotations
3
3
  from dataclasses import dataclass, field
4
4
  from datetime import datetime
5
5
  from typing import TYPE_CHECKING
6
+ from uuid import UUID as StdUUID
6
7
 
7
8
  from elizaos.bootstrap.utils.xml import parse_key_value_xml
9
+ from elizaos.deterministic import get_prompt_reference_datetime
8
10
  from elizaos.generated.spec_helpers import require_action_spec
9
11
  from elizaos.prompts import SCHEDULE_FOLLOW_UP_TEMPLATE
10
12
  from elizaos.types import (
@@ -48,6 +50,25 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
48
50
  return []
49
51
 
50
52
 
53
+ def _normalize_priority(raw_priority: str) -> str:
54
+ normalized = raw_priority.strip().lower()
55
+ if normalized in {"high", "medium", "low"}:
56
+ return normalized
57
+ return "medium"
58
+
59
+
60
+ def _coerce_uuid(value: object | None) -> StdUUID | None:
61
+ if value is None:
62
+ return None
63
+ text = str(value).strip()
64
+ if not text:
65
+ return None
66
+ try:
67
+ return StdUUID(text)
68
+ except ValueError:
69
+ return None
70
+
71
+
51
72
  @dataclass
52
73
  class ScheduleFollowUpAction:
53
74
  name: str = _spec["name"]
@@ -93,7 +114,12 @@ class ScheduleFollowUpAction:
93
114
  )
94
115
 
95
116
  state = await runtime.compose_state(message, ["RECENT_MESSAGES", "ENTITIES"])
96
- state.values["currentDateTime"] = datetime.utcnow().isoformat()
117
+ state.values["currentDateTime"] = get_prompt_reference_datetime(
118
+ runtime,
119
+ message,
120
+ state,
121
+ "action:schedule_follow_up",
122
+ ).isoformat()
97
123
 
98
124
  prompt = runtime.compose_prompt_from_state(
99
125
  state=state,
@@ -114,14 +140,46 @@ class ScheduleFollowUpAction:
114
140
  contact_name = str(parsed.get("contactName", ""))
115
141
  scheduled_at_str = str(parsed.get("scheduledAt", ""))
116
142
  reason = str(parsed.get("reason", "Follow-up"))
117
- priority = str(parsed.get("priority", "medium"))
143
+ priority = _normalize_priority(str(parsed.get("priority", "medium")))
118
144
  follow_up_message = str(parsed.get("message", ""))
145
+ parsed_entity_id = _coerce_uuid(parsed.get("entityId"))
146
+ message_entity_id = _coerce_uuid(message.entity_id)
147
+
148
+ try:
149
+ scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
150
+ except ValueError:
151
+ return ActionResult(
152
+ text="Could not parse the follow-up date/time",
153
+ success=False,
154
+ values={"error": True},
155
+ data={"error": "Invalid follow-up datetime"},
156
+ )
157
+
158
+ entity_id_uuid = parsed_entity_id or message_entity_id
159
+ if entity_id_uuid is None and contact_name:
160
+ contacts = await rolodex_service.search_contacts(search_term=contact_name)
161
+ if contacts:
162
+ entity_id_uuid = contacts[0].entity_id
119
163
 
120
- scheduled_at = datetime.fromisoformat(scheduled_at_str.replace("Z", "+00:00"))
164
+ if entity_id_uuid is None:
165
+ return ActionResult(
166
+ text=f"Could not determine which contact to schedule for ({contact_name}).",
167
+ success=False,
168
+ values={"error": True},
169
+ data={"error": "Missing contact entity id"},
170
+ )
171
+
172
+ contact = await rolodex_service.get_contact(entity_id_uuid)
173
+ if contact is None:
174
+ return ActionResult(
175
+ text=f"Contact '{contact_name}' was not found in the rolodex.",
176
+ success=False,
177
+ values={"error": True},
178
+ data={"error": "Contact not found"},
179
+ )
121
180
 
122
- entity_id = message.entity_id
123
181
  await follow_up_service.schedule_follow_up(
124
- entity_id=entity_id,
182
+ entity_id=entity_id_uuid,
125
183
  scheduled_at=scheduled_at,
126
184
  reason=reason,
127
185
  priority=priority,
@@ -137,11 +195,11 @@ class ScheduleFollowUpAction:
137
195
  text=response_text,
138
196
  success=True,
139
197
  values={
140
- "contactId": str(entity_id),
198
+ "contactId": str(entity_id_uuid),
141
199
  "scheduledAt": scheduled_at.isoformat(),
142
200
  },
143
201
  data={
144
- "contactId": str(entity_id),
202
+ "contactId": str(entity_id_uuid),
145
203
  "contactName": contact_name,
146
204
  "scheduledAt": scheduled_at.isoformat(),
147
205
  "reason": reason,
@@ -2,12 +2,12 @@ from __future__ import annotations
2
2
 
3
3
  import contextlib
4
4
  from dataclasses import dataclass, field
5
- from typing import TYPE_CHECKING
6
- from uuid import UUID
5
+ from typing import TYPE_CHECKING, Any
7
6
 
8
7
  from elizaos.generated.spec_helpers import require_action_spec
9
8
  from elizaos.types import Action, ActionExample, ActionResult, Content
10
9
  from elizaos.types.memory import Memory as MemoryType
10
+ from elizaos.types.primitives import UUID, as_uuid
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from elizaos.types import HandlerCallback, HandlerOptions, IAgentRuntime, Memory, State
@@ -36,6 +36,54 @@ def _convert_spec_examples() -> list[list[ActionExample]]:
36
36
  return []
37
37
 
38
38
 
39
+ def _parse_uuid(value: object) -> UUID | None:
40
+ if not isinstance(value, str) or not value.strip():
41
+ return None
42
+ with contextlib.suppress(Exception):
43
+ return as_uuid(value.strip())
44
+ return None
45
+
46
+
47
+ def _normalize_parameters(options: HandlerOptions | None) -> dict[str, Any]:
48
+ raw = getattr(options, "parameters", None)
49
+ if isinstance(raw, dict):
50
+ return raw
51
+
52
+ if raw is None:
53
+ return {}
54
+
55
+ if hasattr(raw, "items"):
56
+ try:
57
+ return {str(k): v for k, v in raw.items()}
58
+ except Exception:
59
+ return {}
60
+
61
+ return {}
62
+
63
+
64
+ def _coerce_entity_name(entity: object) -> list[str]:
65
+ if isinstance(entity, dict):
66
+ names = entity.get("names")
67
+ if isinstance(names, list):
68
+ return [str(n).strip() for n in names if isinstance(n, str) and n.strip()]
69
+ name = entity.get("name")
70
+ if isinstance(name, str) and name.strip():
71
+ return [name.strip()]
72
+ return []
73
+
74
+ names = getattr(entity, "names", None)
75
+ if isinstance(names, list):
76
+ clean = [str(n).strip() for n in names if isinstance(n, str) and str(n).strip()]
77
+ if clean:
78
+ return clean
79
+
80
+ name = getattr(entity, "name", None)
81
+ if isinstance(name, str) and name.strip():
82
+ return [name.strip()]
83
+
84
+ return []
85
+
86
+
39
87
  @dataclass
40
88
  class SendMessageAction:
41
89
  name: str = _spec["name"]
@@ -58,9 +106,14 @@ class SendMessageAction:
58
106
  callback: HandlerCallback | None = None,
59
107
  responses: list[Memory] | None = None,
60
108
  ) -> ActionResult:
61
- message_text = ""
62
- if responses and responses[0].content:
63
- message_text = str(responses[0].content.text or "")
109
+ params = _normalize_parameters(options)
110
+
111
+ text_param = params.get("text")
112
+ message_text = str(text_param).strip() if isinstance(text_param, str) else ""
113
+ if not message_text and responses and responses[0].content:
114
+ message_text = str(responses[0].content.text or "").strip()
115
+ if not message_text and message.content and isinstance(message.content.text, str):
116
+ message_text = message.content.text.strip()
64
117
 
65
118
  if not message_text:
66
119
  return ActionResult(
@@ -72,18 +125,91 @@ class SendMessageAction:
72
125
 
73
126
  target_room_id = message.room_id
74
127
  target_entity_id: UUID | None = None
128
+ target_type = "room"
129
+
130
+ target_type_param = params.get("targetType") or params.get("target_type")
131
+ target_param = params.get("target")
132
+ source_param = params.get("source")
133
+
134
+ source = (
135
+ source_param.strip()
136
+ if isinstance(source_param, str) and source_param.strip()
137
+ else (
138
+ message.content.source
139
+ if message.content and isinstance(message.content.source, str)
140
+ else "agent"
141
+ )
142
+ )
143
+
144
+ if isinstance(target_type_param, str):
145
+ normalized_target_type = target_type_param.strip().lower()
146
+ if normalized_target_type in {"user", "entity"}:
147
+ target_type = "user"
148
+ elif normalized_target_type == "room":
149
+ target_type = "room"
150
+
151
+ if isinstance(target_param, str) and target_param.strip():
152
+ target_value = target_param.strip()
153
+ if target_type == "room":
154
+ parsed_room = _parse_uuid(target_value)
155
+ if parsed_room:
156
+ target_room_id = parsed_room
157
+ else:
158
+ world_id = None
159
+ room_data = (
160
+ getattr(getattr(state, "data", None), "room", None) if state else None
161
+ )
162
+ if room_data is not None:
163
+ world_id = getattr(room_data, "world_id", None) or getattr(
164
+ room_data, "worldId", None
165
+ )
166
+ if world_id is None:
167
+ with contextlib.suppress(Exception):
168
+ current_room = await runtime.get_room(message.room_id)
169
+ if current_room:
170
+ world_id = getattr(current_room, "world_id", None) or getattr(
171
+ current_room, "worldId", None
172
+ )
173
+
174
+ if world_id is not None:
175
+ with contextlib.suppress(Exception):
176
+ rooms = await runtime.get_rooms(world_id)
177
+ for room in rooms:
178
+ room_name = getattr(room, "name", None)
179
+ if (
180
+ isinstance(room_name, str)
181
+ and room_name.strip().lower() == target_value.lower()
182
+ ):
183
+ room_id = getattr(room, "id", None)
184
+ if room_id is not None:
185
+ target_room_id = as_uuid(str(room_id))
186
+ break
187
+ else:
188
+ parsed_entity = _parse_uuid(target_value)
189
+ if parsed_entity:
190
+ target_entity_id = parsed_entity
191
+ else:
192
+ with contextlib.suppress(Exception):
193
+ entities = await runtime.get_entities_for_room(message.room_id)
194
+ for entity in entities:
195
+ names = _coerce_entity_name(entity)
196
+ if any(name.lower() == target_value.lower() for name in names):
197
+ entity_id = getattr(entity, "id", None)
198
+ if entity_id is not None:
199
+ target_entity_id = as_uuid(str(entity_id))
200
+ break
75
201
 
76
202
  if message.content and message.content.target:
77
203
  target = message.content.target
78
204
  if isinstance(target, dict):
79
205
  room_str = target.get("roomId")
80
206
  entity_str = target.get("entityId")
81
- if room_str:
82
- with contextlib.suppress(ValueError):
83
- target_room_id = UUID(room_str)
84
- if entity_str:
85
- with contextlib.suppress(ValueError):
86
- target_entity_id = UUID(entity_str)
207
+ if room_str and target_type == "room":
208
+ with contextlib.suppress(Exception):
209
+ target_room_id = as_uuid(room_str)
210
+ if entity_str and target_type == "user":
211
+ with contextlib.suppress(Exception):
212
+ target_entity_id = as_uuid(entity_str)
87
213
 
88
214
  if not target_room_id:
89
215
  return ActionResult(
@@ -95,16 +221,28 @@ class SendMessageAction:
95
221
 
96
222
  message_content = Content(
97
223
  text=message_text,
98
- source="agent",
224
+ source=source,
99
225
  actions=["SEND_MESSAGE"],
100
226
  )
101
227
 
228
+ send_message_to_target = getattr(runtime, "send_message_to_target", None)
229
+ if callable(send_message_to_target):
230
+ with contextlib.suppress(Exception):
231
+ from elizaos.types.runtime import TargetInfo
232
+
233
+ await send_message_to_target(
234
+ TargetInfo(
235
+ roomId=str(target_room_id),
236
+ entityId=str(target_entity_id) if target_entity_id else None,
237
+ source=source,
238
+ ),
239
+ message_content,
240
+ )
241
+
102
242
  # Create the message memory
103
243
  import time
104
244
  import uuid as uuid_module
105
245
 
106
- from elizaos.types.primitives import as_uuid
107
-
108
246
  message_memory = MemoryType(
109
247
  id=as_uuid(str(uuid_module.uuid4())),
110
248
  entity_id=runtime.agent_id,
@@ -142,16 +280,25 @@ class SendMessageAction:
142
280
  if callback:
143
281
  await callback(response_content)
144
282
 
283
+ target_id = (
284
+ target_entity_id if target_type == "user" and target_entity_id else target_room_id
285
+ )
145
286
  return ActionResult(
146
- text="Message sent to room",
287
+ text="Message sent",
147
288
  values={
148
289
  "success": True,
149
290
  "messageSent": True,
291
+ "targetType": target_type,
292
+ "target": str(target_id),
293
+ "source": source,
150
294
  "targetRoomId": str(target_room_id),
151
295
  "targetEntityId": str(target_entity_id) if target_entity_id else None,
152
296
  },
153
297
  data={
154
298
  "actionName": "SEND_MESSAGE",
299
+ "targetType": target_type,
300
+ "target": str(target_id),
301
+ "source": source,
155
302
  "targetRoomId": str(target_room_id),
156
303
  "messagePreview": message_text[:100],
157
304
  },
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
- from .action import send_to_admin_action
3
+ from .action import disable_autonomy_action, enable_autonomy_action, send_to_admin_action
4
+ from .evaluators import post_action_evaluator
4
5
  from .providers import admin_chat_provider, autonomy_status_provider
5
6
  from .routes import autonomy_routes
6
7
  from .service import AUTONOMY_SERVICE_TYPE, AutonomyService
@@ -12,6 +13,9 @@ __all__ = [
12
13
  "AutonomyService",
13
14
  "AUTONOMY_SERVICE_TYPE",
14
15
  "send_to_admin_action",
16
+ "enable_autonomy_action",
17
+ "disable_autonomy_action",
18
+ "post_action_evaluator",
15
19
  "admin_chat_provider",
16
20
  "autonomy_status_provider",
17
21
  "autonomy_routes",
@@ -195,3 +195,164 @@ send_to_admin_action = Action(
195
195
  validate=_validate_send_to_admin,
196
196
  handler=_handle_send_to_admin,
197
197
  )
198
+
199
+
200
+ # ── ENABLE_AUTONOMY / DISABLE_AUTONOMY actions ─────────────────────────
201
+
202
+
203
+ def _is_autonomy_running(runtime: IAgentRuntime) -> bool:
204
+ """Return True if the autonomy loop is currently active."""
205
+ svc = runtime.get_service(AUTONOMY_SERVICE_TYPE)
206
+ if svc and isinstance(svc, AutonomyService):
207
+ return svc.is_loop_running()
208
+ return getattr(runtime, "enable_autonomy", False)
209
+
210
+
211
+ async def _validate_enable_autonomy(
212
+ runtime: IAgentRuntime,
213
+ message: Memory,
214
+ _state: State | None = None,
215
+ ) -> bool:
216
+ """Valid only when autonomy is currently paused / disabled."""
217
+ return not _is_autonomy_running(runtime)
218
+
219
+
220
+ async def _validate_disable_autonomy(
221
+ runtime: IAgentRuntime,
222
+ message: Memory,
223
+ _state: State | None = None,
224
+ ) -> bool:
225
+ """Valid only when autonomy is currently running / enabled."""
226
+ return _is_autonomy_running(runtime)
227
+
228
+
229
+ async def _handle_enable_autonomy(
230
+ runtime: IAgentRuntime,
231
+ message: Memory,
232
+ state: State | None = None,
233
+ options: HandlerOptions | None = None,
234
+ callback: Callable[[Content], Awaitable[None]] | None = None,
235
+ responses: list[Memory] | None = None,
236
+ ) -> ActionResult:
237
+ """Enable the autonomous loop."""
238
+ autonomy_service = runtime.get_service(AUTONOMY_SERVICE_TYPE)
239
+
240
+ if not autonomy_service or not isinstance(autonomy_service, AutonomyService):
241
+ runtime.enable_autonomy = True
242
+ result_text = "Autonomy enabled (runtime flag). The autonomy service is not running."
243
+ if callback:
244
+ await callback(Content(text=result_text))
245
+ return ActionResult(success=True, text=result_text, data={"enabled": True})
246
+
247
+ await autonomy_service.enable_autonomy()
248
+ result_text = "Autonomy has been enabled."
249
+ if callback:
250
+ await callback(Content(text=result_text))
251
+ return ActionResult(success=True, text=result_text, data={"enabled": True})
252
+
253
+
254
+ async def _handle_disable_autonomy(
255
+ runtime: IAgentRuntime,
256
+ message: Memory,
257
+ state: State | None = None,
258
+ options: HandlerOptions | None = None,
259
+ callback: Callable[[Content], Awaitable[None]] | None = None,
260
+ responses: list[Memory] | None = None,
261
+ ) -> ActionResult:
262
+ """Disable the autonomous loop."""
263
+ autonomy_service = runtime.get_service(AUTONOMY_SERVICE_TYPE)
264
+
265
+ if not autonomy_service or not isinstance(autonomy_service, AutonomyService):
266
+ runtime.enable_autonomy = False
267
+ result_text = "Autonomy disabled (runtime flag)."
268
+ if callback:
269
+ await callback(Content(text=result_text))
270
+ return ActionResult(success=True, text=result_text, data={"enabled": False})
271
+
272
+ await autonomy_service.disable_autonomy()
273
+ result_text = "Autonomy has been disabled."
274
+ if callback:
275
+ await callback(Content(text=result_text))
276
+ return ActionResult(success=True, text=result_text, data={"enabled": False})
277
+
278
+
279
+ enable_autonomy_action = Action(
280
+ name="ENABLE_AUTONOMY",
281
+ description=(
282
+ "Enable the agent's autonomous operation. "
283
+ "Use this when asked to start autonomy, go autonomous, or activate autonomous behavior. "
284
+ "Only available when autonomy is currently paused."
285
+ ),
286
+ similes=["START_AUTONOMY", "ACTIVATE_AUTONOMY", "GO_AUTONOMOUS"],
287
+ examples=[
288
+ [
289
+ {
290
+ "name": "User",
291
+ "content": {"text": "Enable autonomy"},
292
+ },
293
+ {
294
+ "name": "Agent",
295
+ "content": {
296
+ "text": "Autonomy has been enabled.",
297
+ "action": "ENABLE_AUTONOMY",
298
+ },
299
+ },
300
+ ],
301
+ [
302
+ {
303
+ "name": "User",
304
+ "content": {"text": "Go autonomous"},
305
+ },
306
+ {
307
+ "name": "Agent",
308
+ "content": {
309
+ "text": "Autonomy has been enabled.",
310
+ "action": "ENABLE_AUTONOMY",
311
+ },
312
+ },
313
+ ],
314
+ ],
315
+ validate=_validate_enable_autonomy,
316
+ handler=_handle_enable_autonomy,
317
+ )
318
+
319
+
320
+ disable_autonomy_action = Action(
321
+ name="DISABLE_AUTONOMY",
322
+ description=(
323
+ "Disable the agent's autonomous operation. "
324
+ "Use this when asked to stop, pause, or deactivate autonomous behavior. "
325
+ "Only available when autonomy is currently running."
326
+ ),
327
+ similes=["STOP_AUTONOMY", "PAUSE_AUTONOMY", "DEACTIVATE_AUTONOMY"],
328
+ examples=[
329
+ [
330
+ {
331
+ "name": "User",
332
+ "content": {"text": "Disable autonomy"},
333
+ },
334
+ {
335
+ "name": "Agent",
336
+ "content": {
337
+ "text": "Autonomy has been disabled.",
338
+ "action": "DISABLE_AUTONOMY",
339
+ },
340
+ },
341
+ ],
342
+ [
343
+ {
344
+ "name": "User",
345
+ "content": {"text": "Stop being autonomous"},
346
+ },
347
+ {
348
+ "name": "Agent",
349
+ "content": {
350
+ "text": "Autonomy has been disabled.",
351
+ "action": "DISABLE_AUTONOMY",
352
+ },
353
+ },
354
+ ],
355
+ ],
356
+ validate=_validate_disable_autonomy,
357
+ handler=_handle_disable_autonomy,
358
+ )