mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
157 lines
5.2 KiB
Python
157 lines
5.2 KiB
Python
"""Session persistence, history queries, and message-copying helpers.
|
|
|
|
Uses ``SessionStore`` from ``backend.apps.common.json_store`` for on-disk
|
|
JSON CRUD and exposes higher-level helpers consumed by ``AgentManager``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from datetime import datetime
|
|
from uuid import uuid4
|
|
|
|
from backend.apps.agents.models import AgentSession, Message, MessageBranch
|
|
from backend.apps.common.json_store import SessionStore
|
|
from backend.config.paths import SESSIONS_DIR
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_session_store = SessionStore(SESSIONS_DIR)
|
|
|
|
save_session = _session_store.save
|
|
load_session_data = _session_store.load
|
|
delete_session_file = _session_store.delete
|
|
load_all_session_data = _session_store.load_all
|
|
|
|
|
|
def build_search_text(session: AgentSession, max_len: int = 5000) -> str:
|
|
"""Build a search-indexing string from session name and message content."""
|
|
parts = [session.name or ""]
|
|
for msg in session.messages:
|
|
if msg.role in ("user", "assistant") and isinstance(msg.content, str):
|
|
parts.append(msg.content)
|
|
text = " ".join(parts)
|
|
return text[:max_len]
|
|
|
|
|
|
def get_history(
|
|
q: str = "",
|
|
limit: int = 20,
|
|
offset: int = 0,
|
|
dashboard_id: str | None = None,
|
|
) -> dict:
|
|
"""Return paginated, optionally filtered summaries of closed sessions."""
|
|
all_data = load_all_session_data()
|
|
all_data.sort(key=lambda pair: pair[1].get("closed_at") or "", reverse=True)
|
|
|
|
q_lower = q.strip().lower()
|
|
history: list[dict] = []
|
|
for sid, data in all_data:
|
|
if dashboard_id and data.get("dashboard_id") != dashboard_id:
|
|
continue
|
|
if q_lower:
|
|
name = (data.get("name") or "").lower()
|
|
search_text = (data.get("search_text") or "").lower()
|
|
if q_lower not in name and q_lower not in search_text:
|
|
continue
|
|
history.append({
|
|
"id": data.get("id", sid),
|
|
"name": data.get("name", "Untitled"),
|
|
"status": data.get("status", "stopped"),
|
|
"model": data.get("model", "sonnet"),
|
|
"mode": data.get("mode", "agent"),
|
|
"created_at": data.get("created_at"),
|
|
"closed_at": data.get("closed_at"),
|
|
"cost_usd": data.get("cost_usd", 0),
|
|
"dashboard_id": data.get("dashboard_id"),
|
|
})
|
|
|
|
total = len(history)
|
|
page = history[offset : offset + limit]
|
|
return {
|
|
"sessions": page,
|
|
"total": total,
|
|
"has_more": offset + limit < total,
|
|
}
|
|
|
|
|
|
async def reconcile_on_startup() -> None:
|
|
"""Mark any stale running sessions as stopped."""
|
|
for sid, data in load_all_session_data():
|
|
if data.get("status") in ("running", "waiting_approval"):
|
|
data["status"] = "stopped"
|
|
save_session(sid, data)
|
|
logger.info(f"Marked stale session {sid} as stopped")
|
|
|
|
|
|
def get_browser_agent_children(
|
|
sessions: dict[str, AgentSession],
|
|
parent_session_id: str,
|
|
) -> list[dict]:
|
|
"""Return browser-agent sessions for a parent, from memory or disk."""
|
|
results: list[dict] = []
|
|
seen: set[str] = set()
|
|
|
|
for s in sessions.values():
|
|
if s.mode == "browser-agent" and s.parent_session_id == parent_session_id:
|
|
results.append(s.model_dump(mode="json"))
|
|
seen.add(s.id)
|
|
|
|
for sid, data in load_all_session_data():
|
|
if sid in seen:
|
|
continue
|
|
if data.get("mode") == "browser-agent" and data.get("parent_session_id") == parent_session_id:
|
|
results.append(data)
|
|
|
|
return results
|
|
|
|
|
|
def copy_session_messages(
|
|
source: AgentSession,
|
|
up_to_message_id: str | None = None,
|
|
) -> tuple[list[Message], dict[str, MessageBranch], dict[str, str]]:
|
|
"""Deep-copy messages and branches from *source*, returning new IDs.
|
|
|
|
Returns ``(new_messages, new_branches, old_to_new_msg_id_map)``.
|
|
"""
|
|
source_messages = list(source.messages)
|
|
if up_to_message_id:
|
|
cut_idx = next(
|
|
(i for i, m in enumerate(source_messages) if m.id == up_to_message_id),
|
|
None,
|
|
)
|
|
if cut_idx is not None:
|
|
source_messages = source_messages[: cut_idx + 1]
|
|
|
|
old_to_new: dict[str, str] = {}
|
|
new_messages: list[Message] = []
|
|
for msg in source_messages:
|
|
new_id = uuid4().hex
|
|
old_to_new[msg.id] = new_id
|
|
new_messages.append(Message(
|
|
id=new_id,
|
|
role=msg.role,
|
|
content=msg.content,
|
|
timestamp=msg.timestamp,
|
|
branch_id=msg.branch_id,
|
|
parent_id=old_to_new.get(msg.parent_id) if msg.parent_id else None,
|
|
context_paths=msg.context_paths,
|
|
attached_skills=msg.attached_skills,
|
|
forced_tools=msg.forced_tools,
|
|
images=msg.images,
|
|
))
|
|
|
|
new_branches: dict[str, MessageBranch] = {}
|
|
for bid, branch in source.branches.items():
|
|
new_branches[bid] = MessageBranch(
|
|
id=bid,
|
|
parent_branch_id=branch.parent_branch_id,
|
|
fork_point_message_id=(
|
|
old_to_new.get(branch.fork_point_message_id)
|
|
if branch.fork_point_message_id else None
|
|
),
|
|
created_at=branch.created_at,
|
|
)
|
|
|
|
return new_messages, new_branches, old_to_new
|