mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-08-28 05:15:00 +00:00
196 lines
6 KiB
Python
196 lines
6 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import re
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from openspace.services.runtime_support.settings import get_openspace_config_home_dir
|
|
|
|
|
|
ENTER_PLAN_MODE_TOOL_NAME = "EnterPlanMode"
|
|
EXIT_PLAN_MODE_TOOL_NAME = "ExitPlanMode"
|
|
|
|
PLAN_MODE_ATTACHMENT_TURNS_BETWEEN_ATTACHMENTS = 5
|
|
PLAN_MODE_FULL_REMINDER_EVERY_N_ATTACHMENTS = 5
|
|
|
|
_PLAN_SLUGS_BY_SESSION: dict[str, str] = {}
|
|
_ACTIVE_PLAN_PREFIXES: set[str] = set()
|
|
|
|
|
|
def _session_key(session_id: str | None) -> str:
|
|
return str(session_id or "default")
|
|
|
|
|
|
def _sanitize_slug(value: str) -> str:
|
|
sanitized = re.sub(r"[^a-zA-Z0-9_-]+", "-", value).strip("-").lower()
|
|
return sanitized or "plan"
|
|
|
|
|
|
def get_plans_directory(config_home: str | Path | None = None) -> Path:
|
|
path = get_openspace_config_home_dir(config_home) / "plans"
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
return path
|
|
|
|
|
|
def get_plan_slug(session_id: str | None = None) -> str:
|
|
key = _session_key(session_id)
|
|
cached = _PLAN_SLUGS_BY_SESSION.get(key)
|
|
if cached:
|
|
return cached
|
|
digest = hashlib.sha256(key.encode("utf-8")).hexdigest()[:10]
|
|
prefix = _sanitize_slug(key)[:40]
|
|
slug = f"{prefix}-{digest}" if prefix != digest else digest
|
|
_PLAN_SLUGS_BY_SESSION[key] = slug
|
|
return slug
|
|
|
|
|
|
def set_plan_slug(session_id: str | None, slug: str) -> None:
|
|
_PLAN_SLUGS_BY_SESSION[_session_key(session_id)] = _sanitize_slug(slug)
|
|
|
|
|
|
def clear_plan_slug(session_id: str | None = None) -> None:
|
|
_PLAN_SLUGS_BY_SESSION.pop(_session_key(session_id), None)
|
|
|
|
|
|
def get_plan_file_path(
|
|
session_id: str | None = None,
|
|
agent_id: str | None = None,
|
|
*,
|
|
config_home: str | Path | None = None,
|
|
) -> Path:
|
|
slug = get_plan_slug(session_id)
|
|
suffix = ""
|
|
if agent_id and agent_id != "primary":
|
|
suffix = f"-agent-{_sanitize_slug(str(agent_id))}"
|
|
path = get_plans_directory(config_home) / f"{slug}{suffix}.md"
|
|
register_active_plan_path(path, session_id=session_id)
|
|
return path
|
|
|
|
|
|
def get_plan(
|
|
session_id: str | None = None,
|
|
agent_id: str | None = None,
|
|
*,
|
|
config_home: str | Path | None = None,
|
|
) -> str | None:
|
|
path = get_plan_file_path(session_id, agent_id, config_home=config_home)
|
|
try:
|
|
return path.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return None
|
|
|
|
|
|
def write_plan(
|
|
content: str,
|
|
session_id: str | None = None,
|
|
agent_id: str | None = None,
|
|
*,
|
|
config_home: str | Path | None = None,
|
|
) -> Path:
|
|
path = get_plan_file_path(session_id, agent_id, config_home=config_home)
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(content, encoding="utf-8")
|
|
register_active_plan_path(path, session_id=session_id)
|
|
return path
|
|
|
|
|
|
def register_active_plan_path(path: str | Path, *, session_id: str | None = None) -> None:
|
|
try:
|
|
resolved = Path(path).expanduser().resolve()
|
|
except OSError:
|
|
resolved = Path(path).expanduser().absolute()
|
|
_ACTIVE_PLAN_PREFIXES.add(str(resolved))
|
|
if session_id:
|
|
try:
|
|
base = get_plans_directory() / get_plan_slug(session_id)
|
|
_ACTIVE_PLAN_PREFIXES.add(str(base.expanduser().resolve()))
|
|
except OSError:
|
|
pass
|
|
|
|
|
|
def is_active_plan_file(path: str | Path) -> bool:
|
|
try:
|
|
resolved = str(Path(path).expanduser().resolve())
|
|
except OSError:
|
|
resolved = str(Path(path).expanduser().absolute())
|
|
if resolved in _ACTIVE_PLAN_PREFIXES:
|
|
return True
|
|
for prefix in tuple(_ACTIVE_PLAN_PREFIXES):
|
|
if resolved.startswith(prefix) and resolved.endswith(".md"):
|
|
return True
|
|
return False
|
|
|
|
|
|
def register_plan_mode_permission_predicates() -> None:
|
|
try:
|
|
from openspace.grounding.core.permissions.filesystem import (
|
|
register_internal_path_predicate,
|
|
)
|
|
except Exception:
|
|
return
|
|
|
|
marker = "_openspace_plan_mode_predicates_registered"
|
|
if getattr(register_plan_mode_permission_predicates, marker, False):
|
|
return
|
|
register_internal_path_predicate(
|
|
category="editable",
|
|
reason="Plan files for current session are allowed for writing",
|
|
predicate=is_active_plan_file,
|
|
)
|
|
register_internal_path_predicate(
|
|
category="readable",
|
|
reason="Plan files for current session are allowed for reading",
|
|
predicate=is_active_plan_file,
|
|
)
|
|
setattr(register_plan_mode_permission_predicates, marker, True)
|
|
|
|
|
|
def enter_plan_mode(context: Any) -> dict[str, Any]:
|
|
previous = str(getattr(context, "permission_mode", None) or "default")
|
|
if previous == "plan":
|
|
previous = str(getattr(context, "pre_plan_mode", None) or "default")
|
|
setattr(context, "pre_plan_mode", previous)
|
|
setattr(context, "permission_mode", "plan")
|
|
perm_ctx = getattr(context, "permission_context", None)
|
|
if perm_ctx is not None:
|
|
from dataclasses import replace
|
|
|
|
context.permission_context = replace(
|
|
perm_ctx,
|
|
mode="plan",
|
|
pre_plan_mode=previous,
|
|
)
|
|
session_id = getattr(context, "session_id", None)
|
|
agent_id = getattr(context, "agent_id", None)
|
|
plan_path = get_plan_file_path(session_id, agent_id)
|
|
setattr(context, "plan_file_path", str(plan_path))
|
|
setattr(context, "plan_mode_exited_in_session", False)
|
|
return {
|
|
"previous_mode": previous,
|
|
"plan_file_path": str(plan_path),
|
|
}
|
|
|
|
|
|
def exit_plan_mode(context: Any) -> dict[str, Any]:
|
|
previous = str(getattr(context, "pre_plan_mode", None) or "default")
|
|
if previous == "plan":
|
|
previous = "default"
|
|
setattr(context, "permission_mode", previous)
|
|
setattr(context, "pre_plan_mode", None)
|
|
setattr(context, "plan_mode_exit_pending", True)
|
|
setattr(context, "plan_mode_exited_in_session", True)
|
|
perm_ctx = getattr(context, "permission_context", None)
|
|
if perm_ctx is not None:
|
|
from dataclasses import replace
|
|
|
|
context.permission_context = replace(
|
|
perm_ctx,
|
|
mode=previous,
|
|
pre_plan_mode=None,
|
|
)
|
|
return {"restored_mode": previous}
|
|
|
|
|
|
register_plan_mode_permission_predicates()
|
|
|