@elizaos/python 2.0.0-alpha.10 → 2.0.0-alpha.11
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.
- package/elizaos/__init__.py +0 -1
- package/elizaos/advanced_capabilities/actions/schedule_follow_up.py +70 -15
- package/elizaos/advanced_capabilities/actions/send_message.py +9 -184
- package/elizaos/advanced_memory/memory_service.py +15 -17
- package/elizaos/advanced_planning/planning_service.py +26 -14
- package/elizaos/basic_capabilities/providers/actions.py +118 -29
- package/elizaos/basic_capabilities/providers/character.py +19 -21
- package/elizaos/basic_capabilities/providers/current_time.py +7 -4
- package/elizaos/basic_capabilities/providers/time.py +7 -4
- package/elizaos/bootstrap/__init__.py +21 -2
- package/elizaos/bootstrap/actions/schedule_follow_up.py +65 -7
- package/elizaos/bootstrap/actions/send_message.py +162 -15
- package/elizaos/bootstrap/autonomy/service.py +230 -28
- package/elizaos/bootstrap/providers/actions.py +118 -27
- package/elizaos/bootstrap/providers/agent_settings.py +1 -0
- package/elizaos/bootstrap/providers/attachments.py +1 -0
- package/elizaos/bootstrap/providers/capabilities.py +1 -0
- package/elizaos/bootstrap/providers/character.py +1 -0
- package/elizaos/bootstrap/providers/choice.py +1 -0
- package/elizaos/bootstrap/providers/contacts.py +1 -0
- package/elizaos/bootstrap/providers/current_time.py +8 -2
- package/elizaos/bootstrap/providers/entities.py +1 -0
- package/elizaos/bootstrap/providers/evaluators.py +1 -0
- package/elizaos/bootstrap/providers/facts.py +1 -0
- package/elizaos/bootstrap/providers/follow_ups.py +1 -0
- package/elizaos/bootstrap/providers/knowledge.py +1 -0
- package/elizaos/bootstrap/providers/providers_list.py +1 -0
- package/elizaos/bootstrap/providers/relationships.py +1 -0
- package/elizaos/bootstrap/providers/roles.py +1 -0
- package/elizaos/bootstrap/providers/settings.py +1 -0
- package/elizaos/bootstrap/providers/time.py +8 -4
- package/elizaos/bootstrap/providers/world.py +1 -0
- package/elizaos/deterministic.py +193 -0
- package/elizaos/generated/__init__.py +1 -0
- package/elizaos/generated/action_docs.py +3181 -0
- package/elizaos/generated/spec_helpers.py +175 -0
- package/elizaos/media/mime.py +2 -2
- package/elizaos/media/search.py +23 -23
- package/elizaos/runtime.py +152 -39
- package/elizaos/services/message_service.py +2 -6
- package/elizaos/types/components.py +2 -2
- package/elizaos/types/generated/__init__.py +12 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.py +63 -0
- package/elizaos/types/generated/eliza/v1/agent_pb2.pyi +161 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.py +65 -0
- package/elizaos/types/generated/eliza/v1/components_pb2.pyi +160 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.py +78 -0
- package/elizaos/types/generated/eliza/v1/database_pb2.pyi +305 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/environment_pb2.pyi +135 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.py +82 -0
- package/elizaos/types/generated/eliza/v1/events_pb2.pyi +322 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.py +113 -0
- package/elizaos/types/generated/eliza/v1/ipc_pb2.pyi +367 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.py +41 -0
- package/elizaos/types/generated/eliza/v1/knowledge_pb2.pyi +26 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.py +55 -0
- package/elizaos/types/generated/eliza/v1/memory_pb2.pyi +111 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/message_service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.py +51 -0
- package/elizaos/types/generated/eliza/v1/messaging_pb2.pyi +97 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.py +84 -0
- package/elizaos/types/generated/eliza/v1/model_pb2.pyi +280 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.py +44 -0
- package/elizaos/types/generated/eliza/v1/payment_pb2.pyi +70 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.py +68 -0
- package/elizaos/types/generated/eliza/v1/plugin_pb2.pyi +145 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.py +48 -0
- package/elizaos/types/generated/eliza/v1/primitives_pb2.pyi +92 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/prompts_pb2.pyi +74 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.py +211 -0
- package/elizaos/types/generated/eliza/v1/service_interfaces_pb2.pyi +1296 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/service_pb2.pyi +69 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.py +58 -0
- package/elizaos/types/generated/eliza/v1/settings_pb2.pyi +85 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.py +60 -0
- package/elizaos/types/generated/eliza/v1/state_pb2.pyi +114 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.py +42 -0
- package/elizaos/types/generated/eliza/v1/task_pb2.pyi +58 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.py +52 -0
- package/elizaos/types/generated/eliza/v1/tee_pb2.pyi +90 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.py +39 -0
- package/elizaos/types/generated/eliza/v1/testing_pb2.pyi +23 -0
- package/elizaos/types/model.py +3 -0
- package/elizaos/types/runtime.py +1 -1
- package/package.json +3 -2
- package/tests/test_action_parameters.py +2 -3
- package/tests/test_advanced_memory_behavior.py +0 -2
- package/tests/test_advanced_memory_flag.py +0 -2
- package/tests/test_advanced_planning_behavior.py +11 -5
- package/tests/test_autonomy.py +11 -1
- package/tests/test_runtime.py +8 -17
- package/tests/test_schedule_follow_up_action.py +260 -0
- package/tests/test_send_message_action_targets.py +114 -0
- package/tests/test_settings_crypto.py +0 -2
- package/uv.lock +1565 -0
|
@@ -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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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(
|
|
83
|
-
target_room_id =
|
|
84
|
-
if entity_str:
|
|
85
|
-
with contextlib.suppress(
|
|
86
|
-
target_entity_id =
|
|
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=
|
|
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
|
|
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
|
},
|
|
@@ -11,6 +11,7 @@ import contextlib
|
|
|
11
11
|
import logging
|
|
12
12
|
import time
|
|
13
13
|
import uuid
|
|
14
|
+
from collections.abc import Iterable
|
|
14
15
|
from typing import TYPE_CHECKING, Any
|
|
15
16
|
|
|
16
17
|
from elizaos.bootstrap.services.task import Task
|
|
@@ -23,7 +24,7 @@ from elizaos.prompts import (
|
|
|
23
24
|
from elizaos.types.environment import Room, World
|
|
24
25
|
from elizaos.types.events import EventType
|
|
25
26
|
from elizaos.types.memory import Memory
|
|
26
|
-
from elizaos.types.primitives import UUID, Content, as_uuid
|
|
27
|
+
from elizaos.types.primitives import UUID, Content, as_uuid, string_to_uuid
|
|
27
28
|
from elizaos.types.service import Service
|
|
28
29
|
|
|
29
30
|
from .types import AutonomyStatus
|
|
@@ -40,8 +41,7 @@ AUTONOMY_SERVICE_TYPE = "AUTONOMY"
|
|
|
40
41
|
AUTONOMY_TASK_NAME = "AUTONOMY_THINK"
|
|
41
42
|
|
|
42
43
|
# Tags used for autonomy tasks (parity with TypeScript).
|
|
43
|
-
|
|
44
|
-
AUTONOMY_TASK_TAGS = ["repeat", "autonomy", "internal"]
|
|
44
|
+
AUTONOMY_TASK_TAGS = ["queue", "repeat", "autonomy"]
|
|
45
45
|
|
|
46
46
|
# Default interval in milliseconds
|
|
47
47
|
DEFAULT_INTERVAL_MS = 30_000
|
|
@@ -112,8 +112,10 @@ class AutonomyService(Service):
|
|
|
112
112
|
self._interval_ms = DEFAULT_INTERVAL_MS
|
|
113
113
|
self._task_registered = False
|
|
114
114
|
self._settings_monitor_task: asyncio.Task[None] | None = None
|
|
115
|
-
|
|
115
|
+
# Placeholder; replaced with a deterministic ID during _initialize().
|
|
116
|
+
self._autonomous_room_id = as_uuid("00000000-0000-0000-0000-000000000000")
|
|
116
117
|
self._autonomous_world_id = as_uuid("00000000-0000-0000-0000-000000000001")
|
|
118
|
+
self._autonomy_entity_id = as_uuid("00000000-0000-0000-0000-000000000002")
|
|
117
119
|
|
|
118
120
|
def _log(self, level: str, msg: str) -> None:
|
|
119
121
|
if self._runtime:
|
|
@@ -135,6 +137,9 @@ class AutonomyService(Service):
|
|
|
135
137
|
if not self._runtime:
|
|
136
138
|
return
|
|
137
139
|
|
|
140
|
+
self._autonomous_room_id = as_uuid(
|
|
141
|
+
string_to_uuid(f"autonomy-room-{self._runtime.agent_id}")
|
|
142
|
+
)
|
|
138
143
|
self._log("info", f"Using autonomous room ID: {self._autonomous_room_id}")
|
|
139
144
|
|
|
140
145
|
# Ensure autonomous context exists
|
|
@@ -289,35 +294,223 @@ class AutonomyService(Service):
|
|
|
289
294
|
except Exception:
|
|
290
295
|
return None
|
|
291
296
|
|
|
297
|
+
@staticmethod
|
|
298
|
+
def _coerce_name(entity: object) -> str | None:
|
|
299
|
+
if isinstance(entity, dict):
|
|
300
|
+
names = entity.get("names")
|
|
301
|
+
if isinstance(names, list):
|
|
302
|
+
for name in names:
|
|
303
|
+
if isinstance(name, str) and name.strip():
|
|
304
|
+
return name.strip()
|
|
305
|
+
name = entity.get("name")
|
|
306
|
+
if isinstance(name, str) and name.strip():
|
|
307
|
+
return name.strip()
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
names = getattr(entity, "names", None)
|
|
311
|
+
if isinstance(names, list):
|
|
312
|
+
for name in names:
|
|
313
|
+
if isinstance(name, str) and name.strip():
|
|
314
|
+
return name.strip()
|
|
315
|
+
|
|
316
|
+
name = getattr(entity, "name", None)
|
|
317
|
+
if isinstance(name, str) and name.strip():
|
|
318
|
+
return name.strip()
|
|
319
|
+
|
|
320
|
+
return None
|
|
321
|
+
|
|
322
|
+
@staticmethod
|
|
323
|
+
def _memory_text(memory: Memory) -> str:
|
|
324
|
+
if memory.content and isinstance(memory.content.text, str):
|
|
325
|
+
return memory.content.text.strip()
|
|
326
|
+
return ""
|
|
327
|
+
|
|
328
|
+
async def _build_entity_name_lookup(self, entity_ids: Iterable[UUID]) -> dict[UUID, str]:
|
|
329
|
+
if not self._runtime:
|
|
330
|
+
return {}
|
|
331
|
+
|
|
332
|
+
ids = list({entity_id for entity_id in entity_ids if entity_id})
|
|
333
|
+
if not ids:
|
|
334
|
+
return {}
|
|
335
|
+
|
|
336
|
+
getter = getattr(self._runtime, "get_entities_by_ids", None)
|
|
337
|
+
if not callable(getter):
|
|
338
|
+
return {}
|
|
339
|
+
|
|
340
|
+
try:
|
|
341
|
+
entities = await getter(ids)
|
|
342
|
+
except Exception:
|
|
343
|
+
return {}
|
|
344
|
+
|
|
345
|
+
name_by_id: dict[UUID, str] = {}
|
|
346
|
+
for entity in entities or []:
|
|
347
|
+
entity_id = None
|
|
348
|
+
if isinstance(entity, dict):
|
|
349
|
+
raw_id = entity.get("id")
|
|
350
|
+
if isinstance(raw_id, str):
|
|
351
|
+
with contextlib.suppress(Exception):
|
|
352
|
+
entity_id = as_uuid(raw_id)
|
|
353
|
+
else:
|
|
354
|
+
raw_id = getattr(entity, "id", None)
|
|
355
|
+
if raw_id is not None:
|
|
356
|
+
with contextlib.suppress(Exception):
|
|
357
|
+
entity_id = as_uuid(str(raw_id))
|
|
358
|
+
|
|
359
|
+
if not entity_id:
|
|
360
|
+
continue
|
|
361
|
+
|
|
362
|
+
entity_name = self._coerce_name(entity)
|
|
363
|
+
if entity_name:
|
|
364
|
+
name_by_id[entity_id] = entity_name
|
|
365
|
+
|
|
366
|
+
return name_by_id
|
|
367
|
+
|
|
368
|
+
@staticmethod
|
|
369
|
+
def _dedupe_memories_by_id_keep_earliest(memories: list[Memory]) -> list[Memory]:
|
|
370
|
+
by_id: dict[str, Memory] = {}
|
|
371
|
+
without_id: list[Memory] = []
|
|
372
|
+
|
|
373
|
+
for memory in memories:
|
|
374
|
+
mem_id = str(memory.id) if memory.id else ""
|
|
375
|
+
if not mem_id:
|
|
376
|
+
without_id.append(memory)
|
|
377
|
+
continue
|
|
378
|
+
|
|
379
|
+
existing = by_id.get(mem_id)
|
|
380
|
+
if existing is None or (memory.created_at or 0) < (existing.created_at or 0):
|
|
381
|
+
by_id[mem_id] = memory
|
|
382
|
+
|
|
383
|
+
return [*without_id, *by_id.values()]
|
|
384
|
+
|
|
292
385
|
async def _get_target_room_context_text(self) -> str:
|
|
293
386
|
if not self._runtime:
|
|
294
|
-
return "(no
|
|
387
|
+
return "(no rooms configured)"
|
|
388
|
+
|
|
295
389
|
target_room_id = self._get_target_room_id()
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
390
|
+
|
|
391
|
+
ordered_room_ids: list[UUID] = []
|
|
392
|
+
if target_room_id:
|
|
393
|
+
ordered_room_ids.append(target_room_id)
|
|
394
|
+
|
|
395
|
+
get_participant_rooms = getattr(self._runtime, "get_rooms_for_participant", None)
|
|
396
|
+
if callable(get_participant_rooms):
|
|
397
|
+
with contextlib.suppress(Exception):
|
|
398
|
+
participant_rooms = await get_participant_rooms(self._runtime.agent_id)
|
|
399
|
+
for room_id in participant_rooms or []:
|
|
400
|
+
if room_id not in ordered_room_ids:
|
|
401
|
+
ordered_room_ids.append(room_id)
|
|
402
|
+
|
|
403
|
+
if not ordered_room_ids:
|
|
404
|
+
return "(no rooms configured)"
|
|
405
|
+
|
|
406
|
+
room_name_by_id: dict[UUID, str] = {}
|
|
407
|
+
get_rooms_by_ids = getattr(self._runtime, "get_rooms_by_ids", None)
|
|
408
|
+
if callable(get_rooms_by_ids):
|
|
409
|
+
with contextlib.suppress(Exception):
|
|
410
|
+
rooms = await get_rooms_by_ids(ordered_room_ids)
|
|
411
|
+
for room in rooms or []:
|
|
412
|
+
if room and room.id:
|
|
413
|
+
room_name_by_id[room.id] = (
|
|
414
|
+
room.name if isinstance(room.name, str) and room.name else str(room.id)
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
message_room_ids = [rid for rid in ordered_room_ids if rid != self._autonomous_room_id]
|
|
418
|
+
per_room_limit = 10
|
|
419
|
+
|
|
420
|
+
fetched_messages: list[Memory] = []
|
|
421
|
+
if message_room_ids:
|
|
422
|
+
get_memories_by_room_ids = getattr(self._runtime, "get_memories_by_room_ids", None)
|
|
423
|
+
if callable(get_memories_by_room_ids):
|
|
424
|
+
with contextlib.suppress(Exception):
|
|
425
|
+
fetched_messages = await get_memories_by_room_ids(
|
|
426
|
+
{
|
|
427
|
+
"roomIds": message_room_ids,
|
|
428
|
+
"limit": per_room_limit * len(message_room_ids),
|
|
429
|
+
"tableName": "messages",
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
if not fetched_messages:
|
|
433
|
+
for room_id in message_room_ids:
|
|
434
|
+
with contextlib.suppress(Exception):
|
|
435
|
+
fetched_messages.extend(
|
|
436
|
+
await self._runtime.get_memories(
|
|
437
|
+
{
|
|
438
|
+
"roomId": room_id,
|
|
439
|
+
"count": per_room_limit,
|
|
440
|
+
"tableName": "messages",
|
|
441
|
+
}
|
|
442
|
+
)
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
autonomy_memories = await self._runtime.get_memories(
|
|
446
|
+
{"roomId": self._autonomous_room_id, "count": per_room_limit, "tableName": "memories"}
|
|
300
447
|
)
|
|
301
|
-
|
|
302
|
-
|
|
448
|
+
|
|
449
|
+
external_messages = [
|
|
450
|
+
m
|
|
451
|
+
for m in fetched_messages
|
|
452
|
+
if m and m.entity_id and m.entity_id != self._runtime.agent_id
|
|
453
|
+
]
|
|
454
|
+
entity_name_by_id = await self._build_entity_name_lookup(
|
|
455
|
+
memory.entity_id for memory in external_messages
|
|
303
456
|
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
457
|
+
|
|
458
|
+
messages_by_room: dict[UUID, list[Memory]] = {}
|
|
459
|
+
sorted_messages = sorted(
|
|
460
|
+
self._dedupe_memories_by_id_keep_earliest(external_messages),
|
|
461
|
+
key=lambda m: m.created_at or 0,
|
|
462
|
+
reverse=True,
|
|
463
|
+
)
|
|
464
|
+
for memory in sorted_messages:
|
|
465
|
+
bucket = messages_by_room.setdefault(memory.room_id, [])
|
|
466
|
+
if len(bucket) >= per_room_limit:
|
|
308
467
|
continue
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
468
|
+
bucket.append(memory)
|
|
469
|
+
|
|
470
|
+
room_sections: list[str] = []
|
|
471
|
+
for room_id in message_room_ids:
|
|
472
|
+
room_name = room_name_by_id.get(room_id, str(room_id))
|
|
473
|
+
room_messages = list(reversed(messages_by_room.get(room_id, [])))
|
|
474
|
+
if not room_messages:
|
|
475
|
+
room_sections.append(f"Room: {room_name}\n(no recent messages)")
|
|
476
|
+
continue
|
|
477
|
+
|
|
478
|
+
lines: list[str] = []
|
|
479
|
+
for memory in room_messages:
|
|
480
|
+
text = self._memory_text(memory)
|
|
481
|
+
if not text:
|
|
482
|
+
continue
|
|
483
|
+
author = entity_name_by_id.get(memory.entity_id, str(memory.entity_id))
|
|
484
|
+
lines.append(f"{author}: {text}")
|
|
485
|
+
|
|
486
|
+
if lines:
|
|
487
|
+
room_sections.append(f"Room: {room_name}\n" + "\n".join(lines))
|
|
488
|
+
else:
|
|
489
|
+
room_sections.append(f"Room: {room_name}\n(no recent messages)")
|
|
490
|
+
|
|
491
|
+
autonomy_entries: list[str] = []
|
|
492
|
+
for memory in autonomy_memories:
|
|
493
|
+
text = self._memory_text(memory)
|
|
494
|
+
if not text:
|
|
495
|
+
continue
|
|
496
|
+
|
|
497
|
+
metadata_obj = memory.content.data if memory.content else None
|
|
498
|
+
metadata: dict[str, object] = metadata_obj if isinstance(metadata_obj, dict) else {}
|
|
499
|
+
entry_type = metadata.get("type")
|
|
500
|
+
|
|
501
|
+
if memory.entity_id == self._runtime.agent_id and entry_type == "autonomous-response":
|
|
502
|
+
autonomy_entries.append(f"Thought: {text}")
|
|
503
|
+
elif (
|
|
504
|
+
memory.entity_id == self._autonomy_entity_id and entry_type == "autonomous-trigger"
|
|
505
|
+
):
|
|
506
|
+
autonomy_entries.append(f"Trigger: {text}")
|
|
507
|
+
|
|
508
|
+
if autonomy_entries:
|
|
509
|
+
autonomy_section = "Autonomous context:\n" + "\n".join(autonomy_entries)
|
|
510
|
+
else:
|
|
511
|
+
autonomy_section = "Autonomous context: (none)"
|
|
512
|
+
|
|
513
|
+
return "\n\n".join([*room_sections, autonomy_section])
|
|
321
514
|
|
|
322
515
|
async def _settings_monitoring(self) -> None:
|
|
323
516
|
while not self._is_stopped:
|
|
@@ -412,7 +605,7 @@ class AutonomyService(Service):
|
|
|
412
605
|
else self._create_continuous_prompt(last_thought, is_first_thought, target_context)
|
|
413
606
|
)
|
|
414
607
|
|
|
415
|
-
entity_id =
|
|
608
|
+
entity_id = self._autonomy_entity_id
|
|
416
609
|
current_time_ms = int(time.time() * 1000)
|
|
417
610
|
autonomous_message = Memory(
|
|
418
611
|
id=as_uuid(str(uuid.uuid4())),
|
|
@@ -420,6 +613,15 @@ class AutonomyService(Service):
|
|
|
420
613
|
content=Content(
|
|
421
614
|
text=autonomy_prompt,
|
|
422
615
|
source="autonomy-service",
|
|
616
|
+
data={
|
|
617
|
+
"type": "autonomous-prompt",
|
|
618
|
+
"isAutonomous": True,
|
|
619
|
+
"isInternalThought": True,
|
|
620
|
+
"autonomyMode": mode,
|
|
621
|
+
"channelId": "autonomous",
|
|
622
|
+
"timestamp": current_time_ms,
|
|
623
|
+
"isContinuation": not is_first_thought,
|
|
624
|
+
},
|
|
423
625
|
),
|
|
424
626
|
room_id=self._autonomous_room_id,
|
|
425
627
|
agent_id=self._runtime.agent_id,
|