refactor(server): extract context.py into focused modules

Split the monolithic context.py (117 lines) into five purpose-specific
modules following single-responsibility principle:

- config.py: Server-level constants (DATA_DIR, SESSIONS_DIR, PORT,
  STALE_EVENT_AGE, _state_lock)
- agents.py: Agent-specific paths and caches (CLAUDE_PROJECTS_DIR,
  CODEX_SESSIONS_DIR, discovery caches)
- auth.py: Authentication token generation/validation for spawn endpoint
- spawn_config.py: Spawn feature configuration (PENDING_SPAWNS_DIR,
  rate limiting, projects watcher thread)
- zellij.py: Zellij binary resolution and session management constants

This refactoring improves:
- Code navigation: Find relevant constants by domain, not alphabetically
- Testing: Each module can be tested in isolation
- Import clarity: Mixins import only what they need
- Future maintenance: Changes to one domain don't risk breaking others

All mixins updated to import from new module locations. Tests updated
to use new import paths.

Includes PROPOSED_CODE_FILE_REORGANIZATION_PLAN.md documenting the
rationale and mapping from old to new locations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
teernisse
2026-02-27 11:05:39 -05:00
parent 69175f08f9
commit 1fb4a82b39
20 changed files with 683 additions and 191 deletions

View File

@@ -5,18 +5,99 @@ import subprocess
import time
from datetime import datetime, timezone
from amc_server.context import (
from amc_server.agents import (
CODEX_ACTIVE_WINDOW,
CODEX_SESSIONS_DIR,
SESSIONS_DIR,
_CODEX_CACHE_MAX,
_codex_pane_cache,
_codex_transcript_cache,
_dismissed_codex_ids,
)
from amc_server.config import SESSIONS_DIR
from amc_server.spawn_config import PENDING_SPAWNS_DIR
from amc_server.logging_utils import LOGGER
def _parse_session_timestamp(session_ts):
"""Parse Codex session timestamp to Unix time. Returns None on failure."""
if not session_ts:
return None
try:
# Codex uses ISO format, possibly with Z suffix or +00:00
ts_str = session_ts.replace('Z', '+00:00')
dt = datetime.fromisoformat(ts_str)
return dt.timestamp()
except (ValueError, TypeError, AttributeError):
return None
def _match_pending_spawn(session_cwd, session_start_ts):
"""Match a Codex session to a pending spawn by CWD and timestamp.
Args:
session_cwd: The CWD of the Codex session
session_start_ts: The session's START timestamp (ISO string from Codex metadata)
IMPORTANT: Must be session start time, not file mtime, to avoid false
matches with pre-existing sessions that were recently active.
Returns:
spawn_id if matched (and deletes the pending file), None otherwise
"""
if not PENDING_SPAWNS_DIR.exists():
return None
normalized_cwd = os.path.normpath(session_cwd) if session_cwd else ""
if not normalized_cwd:
return None
# Parse session start time - if we can't parse it, we can't safely match
session_start_unix = _parse_session_timestamp(session_start_ts)
if session_start_unix is None:
return None
try:
for pending_file in PENDING_SPAWNS_DIR.glob('*.json'):
try:
data = json.loads(pending_file.read_text())
if not isinstance(data, dict):
continue
# Check agent type (only match codex to codex)
if data.get('agent_type') != 'codex':
continue
# Check CWD match
pending_path = os.path.normpath(data.get('project_path', ''))
if normalized_cwd != pending_path:
continue
# Check timing: session must have STARTED after spawn was initiated
# Using session start time (not mtime) prevents false matches with
# pre-existing sessions that happen to be recently active
spawn_ts = data.get('timestamp', 0)
if session_start_unix < spawn_ts:
continue
# Match found - claim the spawn_id and delete the pending file
spawn_id = data.get('spawn_id')
try:
pending_file.unlink()
except OSError:
pass
LOGGER.info(
'Matched Codex session (cwd=%s) to pending spawn_id=%s',
session_cwd, spawn_id,
)
return spawn_id
except (json.JSONDecodeError, OSError):
continue
except OSError:
pass
return None
class SessionDiscoveryMixin:
def _discover_active_codex_sessions(self):
"""Find active Codex sessions and create/update session files with Zellij pane info."""
@@ -131,6 +212,13 @@ class SessionDiscoveryMixin:
session_ts = payload.get("timestamp", "")
last_event_at = datetime.fromtimestamp(mtime, tz=timezone.utc).isoformat()
# Check for spawn_id: preserve existing, or match to pending spawn
# Use session_ts (start time) not mtime to avoid false matches
# with pre-existing sessions that were recently active
spawn_id = existing.get("spawn_id")
if not spawn_id:
spawn_id = _match_pending_spawn(cwd, session_ts)
session_data = {
"session_id": session_id,
"agent": "codex",
@@ -145,6 +233,8 @@ class SessionDiscoveryMixin:
"zellij_pane": zellij_pane or existing.get("zellij_pane", ""),
"transcript_path": str(jsonl_file),
}
if spawn_id:
session_data["spawn_id"] = spawn_id
if context_usage:
session_data["context_usage"] = context_usage
elif existing.get("context_usage"):