Files
agentic-mobile-control/apps/device-host-agent/host_agent/history.py
T
2026-07-13 19:45:53 +08:00

106 lines
3.5 KiB
Python

from __future__ import annotations
import json
import sqlite3
from collections.abc import Callable
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
class ConsoleHistoryStore:
def __init__(
self,
db_path: str | Path = "tasks/host_console_history.sqlite3",
*,
limit: int = 200,
now: Callable[[], datetime] | None = None,
) -> None:
self.db_path = Path(db_path)
self.limit = limit
self._now = now or (lambda: datetime.now(UTC))
self.db_path.parent.mkdir(parents=True, exist_ok=True)
self._ensure_schema()
def record_assignment(
self,
*,
task_id: str,
attempt: int,
status: str,
failure_reason: str | None,
device_id: str,
) -> None:
summary = f"{task_id} attempt {attempt} on {device_id}: {status}"
if failure_reason:
summary = f"{summary} ({failure_reason})"
detail = {
"task_id": task_id,
"attempt": attempt,
"status": status,
"failure_reason": failure_reason,
"device_id": device_id,
}
self._insert("assignment", summary, detail)
def record_heartbeat(self, *, device_count: int) -> None:
summary = f"heartbeat: {device_count} devices"
detail = {"device_count": device_count}
self._insert("heartbeat", summary, detail)
def list_recent(self, limit: int | None = None) -> list[dict[str, Any]]:
effective_limit = limit if limit is not None else self.limit
with self._connect() as connection:
rows = connection.execute(
"select kind, occurred_at, summary, detail_json"
" from history_entries order by id desc limit ?",
(effective_limit,),
).fetchall()
return [
{
"kind": row["kind"],
"occurred_at": row["occurred_at"],
"summary": row["summary"],
"detail": json.loads(row["detail_json"]),
}
for row in rows
]
def _insert(self, kind: str, summary: str, detail: dict[str, Any]) -> None:
occurred_at = self._now().isoformat()
with self._connect() as connection:
connection.execute(
"""
insert into history_entries (kind, occurred_at, summary, detail_json)
values (?, ?, ?, ?)
""",
(kind, occurred_at, summary, json.dumps(detail, ensure_ascii=False)),
)
connection.execute(
"""
delete from history_entries where id not in (
select id from history_entries order by id desc limit ?
)
""",
(self.limit,),
)
def _ensure_schema(self) -> None:
with self._connect() as connection:
connection.execute(
"""
create table if not exists history_entries (
id integer primary key autoincrement,
kind text not null,
occurred_at text not null,
summary text not null,
detail_json text not null
)
"""
)
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path)
connection.row_factory = sqlite3.Row
return connection