14#include "absl/status/status.h"
15#include "absl/status/statusor.h"
16#include "absl/strings/ascii.h"
17#include "absl/strings/str_cat.h"
18#include "absl/strings/str_format.h"
19#include "absl/strings/strip.h"
23namespace fs = std::filesystem;
31 if (!path.empty() && path.front() ==
'~') {
32 const char* home =
nullptr;
34 home = std::getenv(
"USERPROFILE");
36 home = std::getenv(
"HOME");
38 if (home !=
nullptr) {
39 path.replace(0, 1, home);
42 return std::filesystem::path(path);
45std::string
Trimmed(
const std::string& value) {
46 return std::string(absl::StripAsciiWhitespace(value));
52 : local_user_(LocalUserName()) {}
54absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
57 if (trimmed.empty()) {
58 return absl::InvalidArgumentError(
"Session name cannot be empty");
73 while (std::filesystem::exists(path) && attempts++ < 5) {
77 if (std::filesystem::exists(path)) {
78 return absl::InternalError(
79 "Unable to allocate a new collaboration session code");
96absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
99 if (normalized.empty()) {
100 return absl::InvalidArgumentError(
"Session code cannot be empty");
108 const auto already_joined = std::find(data.
participants.begin(),
129 return absl::FailedPreconditionError(
"No collaborative session active");
133 absl::Status status = absl::OkStatus();
137 std::filesystem::remove(path, ec);
139 status = absl::InternalError(
140 absl::StrFormat(
"Failed to clean up session file: %s", ec.message()));
152 std::filesystem::remove(path, ec);
154 status = absl::InternalError(absl::StrFormat(
155 "Failed to remove empty session file: %s", ec.message()));
162 status = absl::OkStatus();
174absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
177 return absl::FailedPreconditionError(
"No collaborative session active");
183 absl::Status status = data_or.status();
184 if (absl::IsNotFound(status)) {
206 return absl::InternalError(absl::StrFormat(
207 "Failed to create collaboration directory: %s", ec.message()));
209 return absl::OkStatus();
213 const char* override_name = std::getenv(
"YAZE_USER_NAME");
215 override_name !=
nullptr ? override_name : std::getenv(
"USER");
216 if (user ==
nullptr) {
217 user = std::getenv(
"USERNAME");
219 std::string base = (user !=
nullptr && std::strlen(user) > 0)
221 : std::string(
"Player");
223 const char* host = std::getenv(
"HOSTNAME");
225 if (host ==
nullptr) {
226 host = std::getenv(
"COMPUTERNAME");
229 if (host !=
nullptr && std::strlen(host) > 0) {
230 return absl::StrCat(base,
"@", host);
236 const std::string& input)
const {
237 std::string normalized = Trimmed(input);
239 std::remove_if(normalized.begin(), normalized.end(),
240 [](
unsigned char c) {
241 return !std::isalnum(static_cast<unsigned char>(c));
245 normalized.begin(), normalized.end(), normalized.begin(),
246 [](
unsigned char c) {
247 return static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
253 static constexpr char kAlphabet[] =
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
254 thread_local std::mt19937 rng{std::random_device{}()};
255 std::uniform_int_distribution<size_t> dist(0,
sizeof(kAlphabet) - 2);
257 std::string code(6,
'0');
258 for (
char& ch : code) {
259 ch = kAlphabet[dist(rng)];
266 if (!config_dir.ok()) {
268 return fs::current_path() /
".yaze" /
"agent" /
"sessions";
270 return *config_dir /
"agent" /
"sessions";
274 const std::string& code)
const {
278absl::StatusOr<AgentCollaborationCoordinator::SessionFileData>
280 const std::filesystem::path& path)
const {
281 std::ifstream file(path);
282 if (!file.is_open()) {
283 return absl::NotFoundError(
284 absl::StrFormat(
"Session %s does not exist", path.string()));
291 while (std::getline(file, line)) {
292 auto pos = line.find(
':');
293 if (pos == std::string::npos) {
296 std::string key = line.substr(0, pos);
297 std::string value = Trimmed(line.substr(pos + 1));
300 }
else if (key ==
"code") {
302 }
else if (key ==
"host") {
305 }
else if (key ==
"participant") {
316 if (!data.
host.empty()) {
322 std::rotate(data.
participants.begin(), host_it, std::next(host_it));
330 const std::filesystem::path& path,
const SessionFileData& data)
const {
331 std::ofstream file(path, std::ios::trunc);
332 if (!file.is_open()) {
333 return absl::InternalError(
334 absl::StrFormat(
"Failed to write session file: %s", path.string()));
339 file <<
"host:" << data.
host <<
"\n";
341 std::set<std::string> seen;
342 seen.insert(data.
host);
344 if (seen.insert(participant).second) {
345 file <<
"participant:" << participant <<
"\n";
351 return absl::InternalError(
352 absl::StrFormat(
"Failed to flush session file: %s", path.string()));
354 return absl::OkStatus();
absl::StatusOr< SessionInfo > JoinSession(const std::string &session_code)
std::string LocalUserName() const
absl::StatusOr< SessionFileData > LoadSessionFile(const std::filesystem::path &path) const
std::string GenerateSessionCode() const
std::filesystem::path SessionsDirectory() const
AgentCollaborationCoordinator()
std::string NormalizeSessionCode(const std::string &input) const
std::string session_name_
absl::Status EnsureDirectory() const
absl::Status WriteSessionFile(const std::filesystem::path &path, const SessionFileData &data) const
absl::StatusOr< SessionInfo > HostSession(const std::string &session_name)
const std::string & session_name() const
std::filesystem::path SessionFilePath(const std::string &code) const
absl::StatusOr< SessionInfo > RefreshSession()
absl::Status LeaveSession()
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::filesystem::path ExpandUserPath(std::string path)
std::string Trimmed(const std::string &value)
#define RETURN_IF_ERROR(expr)
std::vector< std::string > participants
std::vector< std::string > participants