chore: anchor fresh-start history to upstream

This commit is contained in:
2026-09-16 15:06:54 +08:00
parent 64a9b43261
commit 012c9c6bed
3514 changed files with 48322 additions and 187346 deletions
+128
View File
@@ -0,0 +1,128 @@
"""Independent, metadata-only project scope for history retrieval.
The caller supplies the current profile's SessionDB and an exact session id.
Only ``status == 'ready'`` authorizes ``allowed_session_ids``. Re-resolve immediately
before returning history and compare ``revision``; this is an optimistic
revision, not a transaction spanning projects.db, state.db, and the filesystem.
No active-project pointer, process cwd, transcript, or plugin data is consulted.
"""
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
from contextlib import closing
from functools import lru_cache
from hermes_cli import projects_db
from hermes_constants import get_hermes_home
from tui_gateway import git_probe
from tui_gateway.project_tree import SessionProjectOwnership, base_name
_EXCLUDED_SOURCES = frozenset({"kanban", "subagent", "tool", "cron"})
def _projects() -> list[dict]:
path = projects_db.projects_db_path()
if not path.exists():
return []
with closing(sqlite3.connect(path.resolve().as_uri() + "?mode=ro", uri=True)) as conn:
conn.row_factory = sqlite3.Row
conn.execute("BEGIN")
return [p.to_dict() for p in projects_db.list_projects(conn, include_archived=True)]
def _ownership(projects: list[dict]) -> SessionProjectOwnership:
# Match Desktop's host policy without importing the RPC/server binding layer.
home = os.path.realpath(os.path.expanduser("~"))
broad = {os.path.normcase(os.path.realpath(p)) for p in
(os.sep, home, os.path.dirname(home), "/home", "/Users") if p}
hermes_home = os.path.normcase(os.path.realpath(str(get_hermes_home())))
def junk_cwd(path):
real = os.path.normcase(os.path.realpath(path))
return not path or real in broad or real == hermes_home
def junk_root(path):
real = os.path.normcase(os.path.realpath(path))
return junk_cwd(path) or real.startswith(hermes_home + os.sep)
return SessionProjectOwnership(
projects, lru_cache(maxsize=None)(git_probe.resolve),
is_junk_root=lru_cache(maxsize=None)(junk_root),
is_junk_cwd=lru_cache(maxsize=None)(junk_cwd),
exists=lru_cache(maxsize=None)(os.path.isdir))
def resolve_project_scope(db, current_session_id: str) -> dict:
"""Resolve one session and enumerate every same-project session metadata row.
Returns JSON-safe ``status``, ``project_key``, ``label``, ``revision``,
``allowed_session_ids`` and ``coverage``. Only ``ready`` authorizes IDs;
``scope_unresolved`` and ``scope_ambiguous`` deny. Database/probe errors
propagate: callers must fail closed, never fall back to unscoped search.
Hidden/background rows are excluded; ordinary archived rows are included.
User IDs match exactly; absent/empty principals match only other absent/empty
principals in this local profile DB, not identified users.
"""
projects = _projects()
ownership = _ownership(projects)
digest = hashlib.sha256()
def stamp(value):
digest.update(json.dumps(value, sort_keys=True, separators=(",", ":"),
ensure_ascii=True).encode("utf-8"))
digest.update(b"\n")
stamp({"version": 1, "profile_home": str(get_hermes_home().resolve()),
"current_session_id": current_session_id})
for project in sorted(projects, key=lambda p: p["id"]):
project["folders"] = sorted(project["folders"], key=lambda f: f["path"])
stamp(project)
buckets: dict[tuple, list[str]] = {}
current = {"status": "session_not_found", "project_key": None, "project_kind": None}
current_principal = None
current_eligible = False
scanned = 0
with db._read_ctx() as conn:
cursor = conn.execute(
"SELECT id, cwd, git_repo_root, git_branch, parent_session_id, profile_name, "
"source, user_id, archived, hidden FROM sessions ORDER BY id")
try:
for raw in cursor:
row = dict(raw)
scanned += 1
assigned = ownership.classify(row)
stamp([row, assigned])
principal = row["user_id"] or None
eligible = not row["hidden"] and row["source"] not in _EXCLUDED_SOURCES
if row["id"] == current_session_id:
current = assigned
current_principal = principal
current_eligible = eligible
if assigned["status"] == "ok" and eligible:
key = (ownership.identity(assigned), principal)
buckets.setdefault(key, []).append(row["id"])
finally:
cursor.close()
status = {"ok": "ready", "ambiguous": "scope_ambiguous"}.get(
current["status"], "scope_unresolved")
if not current_eligible:
status = "scope_unresolved"
allowed = buckets.get((ownership.identity(current), current_principal), []) if status == "ready" else []
project_key = current["project_key"] if status == "ready" else None
label = None
if project_key:
label = next((p["name"] for p in projects if p["id"] == project_key), None)
label = label or base_name(project_key) or project_key
return {
"status": status, "project_key": project_key, "label": label,
"revision": digest.hexdigest(), "allowed_session_ids": allowed,
"coverage": {"metadata_complete": True, "scanned_sessions": scanned,
"allowed_sessions": len(allowed), "archived_included": True,
"excluded_sources": sorted(_EXCLUDED_SOURCES), "hidden_excluded": True,
"principal_policy": "same_user_id" if current_principal else "unknown_local_only",
"project_kind": current["project_kind"], "resolution": current["status"],
"revision_policy": "optimistic_metadata_and_cached_git"}}