80 const std::filesystem::path& project_dir);
103 const std::filesystem::path&
socket_path()
const {
return socket_path_; }
112 nlohmann::json handle_ask_status(
const nlohmann::json& args);
135 void attach_phase_observer(
const std::string& task_id);
142 void detach_phase_observer();
144 void run_async_ask(
const std::string& prompt,
145 const std::string& task_id,
168 void write_sentinel(
const std::string& task_id,
169 const std::string& status);
186 std::filesystem::path async_sentinel_dir()
const;
201 void set_async_sentinel_root(
const std::filesystem::path& root);
208 void cleanup_expired_tasks();
223 std::string status =
"queued";
224 std::string phase =
"queued";
226 std::chrono::steady_clock::time_point
created;
252 return active_task_id_;
261 void subscribe(
int fd);
269 void unsubscribe(
int fd);
282 void broadcast_notification(
const nlohmann::json& notif);
291 std::lock_guard<std::mutex> lock(subscribers_mutex_);
292 return subscribers_.size();
308 void update_task_phase(
const std::string& task_id,
309 const std::string& status,
310 const std::string& phase);
326 return observer_gen_.load() != attached_gen_;
343 void serve_client(
int client_fd);
353 std::string dispatch(
const std::string& request,
int client_fd);
365 void reap_finished_clients_locked();
369 std::filesystem::path socket_path_;
370 std::string bound_canonical_;
372 std::atomic<bool> running_{
false};
373 std::thread accept_thread_;
383 struct ClientThread {
385 std::atomic<bool> finished{
false};
388 std::vector<std::unique_ptr<ClientThread>> client_threads_;
389 mutable std::mutex client_threads_mutex_;
392 std::unordered_map<std::string, AsyncTask> tasks_;
399 std::unordered_set<int> subscribers_;
400 mutable std::mutex subscribers_mutex_;
407 std::string active_task_id_;
413 std::atomic<uint64_t> observer_gen_{0};
418 uint64_t attached_gen_ = 0;
423 std::filesystem::path async_sentinel_root_override_;