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"
25namespace fs = std::filesystem;
32std::filesystem::path ExpandUserPath(std::string path) {
33 if (!path.empty() && path.front() ==
'~') {
34 const char* home =
nullptr;
36 home = std::getenv(
"USERPROFILE");
38 home = std::getenv(
"HOME");
40 if (home !=
nullptr) {
41 path.replace(0, 1, home);
44 return std::filesystem::path(path);
47std::string
Trimmed(
const std::string& value) {
48 return std::string(absl::StripAsciiWhitespace(value));
54 : local_user_(LocalUserName()) {}
56absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
59 if (trimmed.empty()) {
60 return absl::InvalidArgumentError(
"Session name cannot be empty");
75 while (std::filesystem::exists(path) && attempts++ < 5) {
79 if (std::filesystem::exists(path)) {
80 return absl::InternalError(
81 "Unable to allocate a new collaboration session code");
98absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
101 if (normalized.empty()) {
102 return absl::InvalidArgumentError(
"Session code cannot be empty");
110 const auto already_joined = std::find(data.
participants.begin(),
131 return absl::FailedPreconditionError(
"No collaborative session active");
135 absl::Status status = absl::OkStatus();
139 std::filesystem::remove(path, ec);
141 status = absl::InternalError(
142 absl::StrFormat(
"Failed to clean up session file: %s", ec.message()));
154 std::filesystem::remove(path, ec);
156 status = absl::InternalError(absl::StrFormat(
157 "Failed to remove empty session file: %s", ec.message()));
164 status = absl::OkStatus();
176absl::StatusOr<AgentCollaborationCoordinator::SessionInfo>
179 return absl::FailedPreconditionError(
"No collaborative session active");
185 absl::Status status = data_or.status();
186 if (absl::IsNotFound(status)) {
208 return absl::InternalError(absl::StrFormat(
209 "Failed to create collaboration directory: %s", ec.message()));
211 return absl::OkStatus();
215 const char* override_name = std::getenv(
"YAZE_USER_NAME");
216 const char* user = override_name !=
nullptr ? override_name : std::getenv(
"USER");
217 if (user ==
nullptr) {
218 user = std::getenv(
"USERNAME");
220 std::string base = (user !=
nullptr && std::strlen(user) > 0)
222 : std::string(
"Player");
224 const char* host = std::getenv(
"HOSTNAME");
226 if (host ==
nullptr) {
227 host = std::getenv(
"COMPUTERNAME");
230 if (host !=
nullptr && std::strlen(host) > 0) {
231 return absl::StrCat(base,
"@", host);
237 const std::string& input)
const {
238 std::string normalized = Trimmed(input);
239 normalized.erase(std::remove_if(normalized.begin(), normalized.end(),
240 [](
unsigned char c) {
241 return !std::isalnum(
242 static_cast<unsigned char>(c));
245 std::transform(normalized.begin(), normalized.end(), normalized.begin(),
246 [](
unsigned char c) {
247 return static_cast<char>(
248 std::toupper(static_cast<unsigned char>(c)));
254 static constexpr char kAlphabet[] =
"ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
255 thread_local std::mt19937 rng{std::random_device{}()};
256 std::uniform_int_distribution<size_t> dist(0,
sizeof(kAlphabet) - 2);
258 std::string code(6,
'0');
259 for (
char& ch : code) {
260 ch = kAlphabet[dist(rng)];
267 if (!config_dir.ok()) {
269 return fs::current_path() /
".yaze" /
"agent" /
"sessions";
271 return *config_dir /
"agent" /
"sessions";
275 const std::string& code)
const {
279absl::StatusOr<AgentCollaborationCoordinator::SessionFileData>
281 const std::filesystem::path& path)
const {
282 std::ifstream file(path);
283 if (!file.is_open()) {
284 return absl::NotFoundError(
285 absl::StrFormat(
"Session %s does not exist", path.string()));
292 while (std::getline(file, line)) {
293 auto pos = line.find(
':');
294 if (pos == std::string::npos) {
297 std::string key = line.substr(0, pos);
298 std::string value = Trimmed(line.substr(pos + 1));
301 }
else if (key ==
"code") {
303 }
else if (key ==
"host") {
306 }
else if (key ==
"participant") {
317 if (!data.
host.empty()) {
332 const std::filesystem::path& path,
const SessionFileData& data)
const {
333 std::ofstream file(path, std::ios::trunc);
334 if (!file.is_open()) {
335 return absl::InternalError(
336 absl::StrFormat(
"Failed to write session file: %s", path.string()));
341 file <<
"host:" << data.
host <<
"\n";
343 std::set<std::string> seen;
344 seen.insert(data.
host);
346 if (seen.insert(participant).second) {
347 file <<
"participant:" << participant <<
"\n";
353 return absl::InternalError(
354 absl::StrFormat(
"Failed to flush session file: %s", path.string()));
356 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 RETURN_IF_ERROR(expression)
#define ASSIGN_OR_RETURN(type_variable_name, expression)
std::string Trimmed(const std::string &value)
Main namespace for the application.
std::vector< std::string > participants
std::vector< std::string > participants