#pragma once
#include <atomic>
#include <array>
#include <chrono>
#include <cstdint>
#include <filesystem>
#include <mutex>
#include <optional>
#include <string>
#include "engine_process.hpp"
#include "game_record_store.hpp"
#include "observation_store.hpp"
#include "server_config.hpp"
namespace nethack_mcp
{
/// Result returned by one MCP tool implementation.
struct ToolResult
{
/// Report whether the requested tool operation completed.
bool success = false;
/// Return the complete state associated with the operation.
Json value = Json::object();
/// Stable machine-readable error code when `success` is false.
std::string code;
/// Human-readable error detail when `success` is false.
std::string message;
};
/// Own one isolated game worker and serialize its gameplay input.
class GameSession
{
public:
/// Create a session with a fixed public ID and one-time control secret.
GameSession(std::filesystem::path data_root,
std::filesystem::path runtime_dir,
std::string game_id,
std::string control_token,
std::string character_name,
std::string model_slug,
std::chrono::steady_clock::time_point created_at,
std::shared_ptr<GameRecordStore> records,
const ServerConfig& config);
GameSession(const GameSession&) = delete;
GameSession& operator=(const GameSession&) = delete;
/// Start this game's worker and wait for its first boundary.
ToolResult startGame(const Json& arguments);
/// Authorize and refresh one game-specific agent call.
ToolResult authorize(const Json& arguments);
/// Return the latest state, optionally waiting for a revision.
ToolResult observe(const Json& arguments);
/// Answer a pending one-key input boundary.
ToolResult press(const Json& arguments);
/// Answer a pending text, choice, command, or acknowledgement boundary.
ToolResult respond(const Json& arguments);
/// Answer a pending menu boundary with complete selections.
ToolResult selectMenu(const Json& arguments);
/// Administratively stop the worker and retain its final state.
ToolResult quitGame(const Json& arguments);
/// Stop any active worker during server shutdown.
void shutdown();
/// Mark a game terminal when its idle or absolute deadline has passed.
bool expireIfNeeded();
/// Reap a completed worker and remove its private runtime directory.
bool cleanup();
/// Report whether this game has reached a terminal transition.
bool terminal() const;
/// Report whether the worker child is still running.
bool workerRunning() const;
/// Return the earlier idle or absolute lifetime deadline.
std::chrono::steady_clock::time_point nextDeadline() const;
/// Return the immutable UUID associated with this session.
const std::string& gameId() const;
/// Return the latest state for the spectator server.
Json snapshot() const;
private:
ToolResult sendInput(const Json& arguments, Json response,
const std::string& expected_kind);
ToolResult errorResult(std::string code, std::string message) const;
ToolResult refreshAgentActivity(std::int64_t& activity_at_s);
bool persistAgentActivity(std::int64_t activity_at_s);
void handleWorkerMessage(const Json& message);
bool copyRuntimeFiles(const std::filesystem::path& run_directory,
std::string& error) const;
static bool validGameId(const Json& arguments,
const Json& state,
std::string& error);
static bool parseKey(const Json& key, int& value, std::string& error);
bool markTerminal(const std::string& reason,
const std::string& end_time_kind,
const std::string& lifecycle);
bool finishRecord(const std::string& reason,
const std::string& end_time_kind,
std::int64_t ended_at_s);
static std::int64_t wallClockSeconds();
std::filesystem::path data_root_;
std::filesystem::path runtime_dir_;
std::string game_id_;
std::string character_name_;
std::string model_slug_;
std::shared_ptr<GameRecordStore> records_;
std::chrono::steady_clock::time_point created_at_;
std::chrono::steady_clock::time_point last_agent_activity_;
std::chrono::seconds idle_timeout_;
std::chrono::seconds max_game_duration_;
std::array<unsigned char, 32> control_salt_{};
std::array<unsigned char, 32> control_digest_{};
std::size_t max_worker_output_bytes_;
std::size_t max_character_name_bytes_;
ObservationStore observations_;
std::unique_ptr<EngineProcess> process_;
mutable std::mutex action_mutex_;
mutable std::mutex lifecycle_mutex_;
bool closing_ = false;
std::optional<std::string> end_reason_;
std::optional<std::string> end_time_kind_;
std::int64_t terminal_at_s_ = 0;
std::optional<std::string> terminal_result_;
std::optional<int> last_depth_;
std::atomic<bool> shutdown_requested_ = false;
std::uint64_t operation_counter_ = 0;
};
} // namespace nethack_mcp