forked from Rockachopa/Timmy-time-dashboard
Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
"""Deep focus mode — single-problem context for Timmy.
|
|
|
|
Persists focus state to a JSON file so Timmy can maintain narrow,
|
|
deep attention on one problem across session restarts.
|
|
|
|
Usage:
|
|
from timmy.focus import focus_manager
|
|
|
|
focus_manager.set_topic("three-phase loop")
|
|
topic = focus_manager.get_topic() # "three-phase loop"
|
|
ctx = focus_manager.get_focus_context() # prompt injection string
|
|
focus_manager.clear()
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
_DEFAULT_STATE_DIR = Path.home() / ".timmy"
|
|
_STATE_FILE = "focus.json"
|
|
|
|
|
|
class FocusManager:
|
|
"""Manages deep-focus state with file-backed persistence."""
|
|
|
|
def __init__(self, state_dir: Path | None = None) -> None:
|
|
self._state_dir = state_dir or _DEFAULT_STATE_DIR
|
|
self._state_file = self._state_dir / _STATE_FILE
|
|
self._topic: str | None = None
|
|
self._mode: str = "broad"
|
|
self._load()
|
|
|
|
# ── Public API ────────────────────────────────────────────────
|
|
|
|
def get_topic(self) -> str | None:
|
|
"""Return the current focus topic, or None if unfocused."""
|
|
return self._topic
|
|
|
|
def get_mode(self) -> str:
|
|
"""Return 'deep' or 'broad'."""
|
|
return self._mode
|
|
|
|
def is_focused(self) -> bool:
|
|
"""True when deep-focus is active with a topic set."""
|
|
return self._mode == "deep" and self._topic is not None
|
|
|
|
def set_topic(self, topic: str) -> None:
|
|
"""Activate deep focus on a specific topic."""
|
|
self._topic = topic.strip()
|
|
self._mode = "deep"
|
|
self._save()
|
|
logger.info("Focus: deep-focus set → %r", self._topic)
|
|
|
|
def clear(self) -> None:
|
|
"""Return to broad (unfocused) mode."""
|
|
old = self._topic
|
|
self._topic = None
|
|
self._mode = "broad"
|
|
self._save()
|
|
logger.info("Focus: cleared (was %r)", old)
|
|
|
|
def get_focus_context(self) -> str:
|
|
"""Return a prompt-injection string for the current focus state.
|
|
|
|
When focused, this tells the model to prioritize the topic.
|
|
When broad, returns an empty string (no injection).
|
|
"""
|
|
if not self.is_focused():
|
|
return ""
|
|
return (
|
|
f"[DEEP FOCUS MODE] You are currently in deep-focus mode on: "
|
|
f'"{self._topic}". '
|
|
f"Prioritize this topic in your responses. Surface related memories "
|
|
f"and prior conversation about this topic first. Deprioritize "
|
|
f"unrelated context. Stay focused — depth over breadth."
|
|
)
|
|
|
|
# ── Persistence ───────────────────────────────────────────────
|
|
|
|
def _load(self) -> None:
|
|
"""Load focus state from disk."""
|
|
if not self._state_file.exists():
|
|
return
|
|
try:
|
|
data = json.loads(self._state_file.read_text())
|
|
self._topic = data.get("topic")
|
|
self._mode = data.get("mode", "broad")
|
|
except Exception as exc:
|
|
logger.warning("Focus: failed to load state: %s", exc)
|
|
|
|
def _save(self) -> None:
|
|
"""Persist focus state to disk."""
|
|
try:
|
|
self._state_dir.mkdir(parents=True, exist_ok=True)
|
|
self._state_file.write_text(
|
|
json.dumps({"topic": self._topic, "mode": self._mode}, indent=2)
|
|
)
|
|
except Exception as exc:
|
|
logger.warning("Focus: failed to save state: %s", exc)
|
|
|
|
|
|
# Module-level singleton
|
|
focus_manager = FocusManager()
|