Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions examples/skills/ov_dream/scripts/dream.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,27 @@ def get_active_session(openclaw_root: Path) -> Session | None:
return sessions[0] if sessions else None


def _message_text(content: Any) -> str:
"""Flatten an OpenClaw message body to plain text.

`content` is a block list in most rows, but real transcripts also store a
bare string for simple messages. Iterating a string yields characters, and
`"p".get(...)` raised AttributeError out of `parse_messages()` — which
aborted the whole run, so a single such message meant no session was ever
committed. Blocks that are not dicts are skipped for the same reason.
"""
if isinstance(content, str):
return content.strip()
if not isinstance(content, list):
return ""
parts = [
block.get("text", "").strip()
for block in content
if isinstance(block, dict) and block.get("type") == "text"
]
return "\n".join(part for part in parts if part)


def parse_messages(
sessions_root: Path, session: Session, after_timestamp: str | None
) -> Iterable[Message]:
Expand All @@ -284,11 +305,7 @@ def parse_messages(
role = message.get("role")
if role not in {"user", "assistant"}:
continue
blocks = message.get("content", [])
text_parts = [
block.get("text", "").strip() for block in blocks if block.get("type") == "text"
]
content = "\n".join(part for part in text_parts if part)
content = _message_text(message.get("content", []))
if not content:
continue
messages.append(Message(role=role, content=content, timestamp=timestamp))
Expand Down
16 changes: 14 additions & 2 deletions examples/skills/ov_dream/tests/test_dream_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,17 @@ def test_sync_active_session_syncs_chat_sessions_with_independent_cursors(tmp_pa
"main",
[("user", "hello from main", "2026-04-20T00:01:00Z")],
)
with main_file.open("a", encoding="utf-8") as session_file:
session_file.write(
json.dumps(
{
"type": "message",
"timestamp": "2026-04-20T00:01:30Z",
"message": {"role": "assistant", "content": "plain string from main"},
}
)
+ "\n"
)
_write_session(
direct_file,
"direct",
Expand Down Expand Up @@ -300,16 +311,17 @@ def commit_session(self, session_id, wait=True):
summary = dream.sync_active_session(client, openclaw_root, state_root)

assert summary["session_count"] == 2
assert summary["synced_count"] == 2
assert summary["synced_count"] == 3
assert client.messages == [
("main", "user", "hello from main"),
("main", "assistant", "plain string from main"),
("direct", "assistant", "hello from direct"),
]
assert client.commits == [("main", True), ("direct", True)]

state = json.loads((state_root / "ov_dream_sync.json").read_text(encoding="utf-8"))
assert state["sessions"]["main"]["session_key"] == "agent:main:main"
assert state["sessions"]["main"]["last_synced_timestamp"] == "2026-04-20T00:01:00Z"
assert state["sessions"]["main"]["last_synced_timestamp"] == "2026-04-20T00:01:30Z"
assert state["sessions"]["direct"]["session_key"] == "agent:main:telegram:direct:123"
assert state["sessions"]["direct"]["last_synced_timestamp"] == "2026-04-20T00:02:00Z"
assert "cron" not in state["sessions"]