# session_logger.py """ Opens timestamped log/script files at startup and keeps them open for the lifetime of the process. The next run of the GUI creates new files; the previous run's files are simply closed when the process exits. File layout ----------- logs/ comms_.log - every comms entry (direction/kind/payload) as JSON-L toolcalls_.log - sequential record of every tool invocation scripts/generated/ _.ps1 - each PowerShell script the AI generated, in order Where = YYYYMMDD_HHMMSS of when this session was started. """ import datetime import json import threading from pathlib import Path _LOG_DIR = Path("./logs") _SCRIPTS_DIR = Path("./scripts/generated") _ts: str = "" # session timestamp string e.g. "20260301_142233" _seq: int = 0 # monotonic counter for script files this session _seq_lock = threading.Lock() _comms_fh = None # file handle: logs/comms_.log _tool_fh = None # file handle: logs/toolcalls_.log def _now_ts() -> str: return datetime.datetime.now().strftime("%Y%m%d_%H%M%S") def open_session(): """ Called once at GUI startup. Creates the log directories if needed and opens the two log files for this session. Idempotent - a second call is ignored. """ global _ts, _comms_fh, _tool_fh, _seq if _comms_fh is not None: return # already open _LOG_DIR.mkdir(parents=True, exist_ok=True) _SCRIPTS_DIR.mkdir(parents=True, exist_ok=True) _ts = _now_ts() _seq = 0 _comms_fh = open(_LOG_DIR / f"comms_{_ts}.log", "w", encoding="utf-8", buffering=1) _tool_fh = open(_LOG_DIR / f"toolcalls_{_ts}.log", "w", encoding="utf-8", buffering=1) _tool_fh.write(f"# Tool-call log — session {_ts}\n\n") _tool_fh.flush() def close_session(): """Flush and close both log files. Called on clean exit (optional).""" global _comms_fh, _tool_fh if _comms_fh: _comms_fh.close() _comms_fh = None if _tool_fh: _tool_fh.close() _tool_fh = None def log_comms(entry: dict): """ Append one comms entry to the comms log file as a JSON-L line. Thread-safe (GIL + line-buffered file). """ if _comms_fh is None: return try: _comms_fh.write(json.dumps(entry, ensure_ascii=False, default=str) + "\n") except Exception: pass def log_tool_call(script: str, result: str, script_path: str | None): """ Append a tool-call record to the toolcalls log and write the PS1 script to scripts/generated/. Returns the path of the written script file. """ global _seq if _tool_fh is None: return script_path # logger not open yet with _seq_lock: _seq += 1 seq = _seq ts_entry = datetime.datetime.now().strftime("%H:%M:%S") # Write the .ps1 file ps1_name = f"{_ts}_{seq:04d}.ps1" ps1_path = _SCRIPTS_DIR / ps1_name try: ps1_path.write_text(script, encoding="utf-8") except Exception as exc: ps1_path = None ps1_name = f"(write error: {exc})" # Append to the tool-call sequence log (script body omitted - see .ps1 file) try: _tool_fh.write( f"## Call #{seq} [{ts_entry}]\n" f"Script file: {ps1_path}\n\n" f"### Result\n\n" f"```\n{result}\n```\n\n" f"---\n\n" ) _tool_fh.flush() except Exception: pass return str(ps1_path) if ps1_path else None