1616#include " ../provider/apply_model_to_session.hpp"
1717#include " ../provider/cwd_model_override.hpp"
1818#include " ../provider/model_resolver.hpp"
19+ #include " ../feedback/feedback_upload.hpp"
1920#include " ../tool/mcp_manager.hpp"
2021#include " ../tool/ace_browser_bridge/browser_tools.hpp"
2122#include " ../tool/tool_executor.hpp"
2728#include " ../session/thread_goal_store.hpp"
2829#include " ../utils/logger.hpp"
2930#include " ../utils/terminal_title.hpp"
31+ #include " ../utils/utf8_path.hpp"
32+ #include " version.hpp"
3033#include < algorithm>
3134#include < cctype>
3235#include < chrono>
36+ #include < filesystem>
37+ #include < fstream>
3338#include < limits>
3439#include < mutex>
3540#include < optional>
4045
4146namespace acecode {
4247
48+ namespace fs = std::filesystem;
49+
4350namespace {
4451
4552std::string trim_ascii_command (std::string s) {
@@ -241,6 +248,7 @@ static void cmd_help(CommandContext& ctx, const std::string& /*args*/) {
241248 << " /memory - List, view, edit, forget, or reload persistent user memory\n "
242249 << " /init - Generate an ACECODE.md skeleton in the current directory\n "
243250 << " /history - List or clear the per-working-directory input history\n "
251+ << " /feedback - Upload current session diagnostics to the configured upgrade service\n "
244252 << " /models - Inspect bundled models.dev registry\n "
245253 << " /browser - Show or toggle ACE Browser Bridge tools for this session\n "
246254 << " /proxy - Show or switch the HTTP proxy used for LLM/API requests\n "
@@ -258,6 +266,119 @@ static void cmd_help(CommandContext& ctx, const std::string& /*args*/) {
258266 ctx.state .chat_follow_tail = true ;
259267}
260268
269+ static bool ensure_empty_file_exists (const fs::path& path, std::string* error) {
270+ std::error_code ec;
271+ fs::create_directories (path.parent_path (), ec);
272+ if (ec) {
273+ if (error) *error = " failed to create session directory: " + ec.message ();
274+ return false ;
275+ }
276+ if (fs::exists (path, ec)) return true ;
277+ std::ofstream ofs (path, std::ios::binary | std::ios::app);
278+ if (!ofs) {
279+ if (error) *error = " failed to create session JSONL file: " + path_to_utf8 (path);
280+ return false ;
281+ }
282+ return true ;
283+ }
284+
285+ static std::string feedback_error_with_package (const std::string& error,
286+ const std::string& upload_url,
287+ const fs::path& package_path) {
288+ std::ostringstream oss;
289+ oss << " Feedback upload failed: " << error
290+ << " \n Upload URL: " << upload_url;
291+ if (!package_path.empty ()) {
292+ oss << " \n Package retained: " << path_to_utf8 (package_path);
293+ }
294+ return oss.str ();
295+ }
296+
297+ static void cmd_feedback (CommandContext& ctx, const std::string& raw_args) {
298+ const std::string feedback_text = trim_ascii_command (raw_args);
299+
300+ if (!ctx.session_manager ) {
301+ std::lock_guard<std::mutex> lk (ctx.state .mu );
302+ emit_system_message_locked (ctx.state , " Feedback upload failed: session persistence is not available." );
303+ return ;
304+ }
305+
306+ if (!is_valid_upgrade_base_url (ctx.config .upgrade .base_url )) {
307+ std::lock_guard<std::mutex> lk (ctx.state .mu );
308+ emit_system_message_locked (
309+ ctx.state ,
310+ " Feedback upload failed: upgrade.base_url must be a non-empty http or https URL." );
311+ return ;
312+ }
313+
314+ const std::string upload_url = normalize_upgrade_base_url (ctx.config .upgrade .base_url );
315+ const std::string session_id = ctx.session_manager ->ensure_active_session_id ();
316+ if (session_id.empty ()) {
317+ std::lock_guard<std::mutex> lk (ctx.state .mu );
318+ emit_system_message_locked (ctx.state , " Feedback upload failed: active session is not available." );
319+ return ;
320+ }
321+
322+ const std::string project_dir = SessionStorage::get_project_dir (ctx.cwd );
323+ const fs::path session_jsonl =
324+ path_from_utf8 (SessionStorage::session_path (project_dir, session_id));
325+ std::string session_file_error;
326+ if (!ensure_empty_file_exists (session_jsonl, &session_file_error)) {
327+ std::lock_guard<std::mutex> lk (ctx.state .mu );
328+ emit_system_message_locked (ctx.state , " Feedback upload failed: " + session_file_error);
329+ return ;
330+ }
331+
332+ {
333+ std::lock_guard<std::mutex> lk (ctx.state .mu );
334+ emit_system_message_locked (
335+ ctx.state ,
336+ " Preparing feedback package for upload to " + upload_url);
337+ }
338+ if (ctx.post_event ) ctx.post_event ();
339+
340+ const fs::path log_path = path_from_utf8 (ctx.cwd ) / " acecode.log" ;
341+ acecode::feedback::FeedbackPackageRequest package_req;
342+ package_req.feedback_text = feedback_text;
343+ package_req.session_id = session_id;
344+ package_req.session_jsonl_path = session_jsonl;
345+ package_req.log_path = log_path;
346+ package_req.acecode_version = ACECODE_VERSION ;
347+
348+ auto package = acecode::feedback::build_feedback_package (package_req);
349+ if (!package.ok ) {
350+ {
351+ std::lock_guard<std::mutex> lk (ctx.state .mu );
352+ emit_system_message_locked (ctx.state , " Feedback upload failed: " + package.error );
353+ }
354+ if (ctx.post_event ) ctx.post_event ();
355+ return ;
356+ }
357+
358+ acecode::feedback::FeedbackUploadRequest upload_req;
359+ upload_req.upload_url = upload_url;
360+ upload_req.package_path = package.package_path ;
361+ upload_req.package_filename = package.package_filename ;
362+ upload_req.timeout_ms = ctx.config .upgrade .timeout_ms ;
363+
364+ auto upload = acecode::feedback::upload_feedback_package (upload_req);
365+ {
366+ std::lock_guard<std::mutex> lk (ctx.state .mu );
367+ if (upload.ok ) {
368+ std::error_code ec;
369+ fs::remove (package.package_path , ec);
370+ emit_system_message_locked (
371+ ctx.state ,
372+ " Feedback uploaded: " + package.package_filename );
373+ } else {
374+ emit_system_message_locked (
375+ ctx.state ,
376+ feedback_error_with_package (upload.error , upload_url, package.package_path ));
377+ }
378+ }
379+ if (ctx.post_event ) ctx.post_event ();
380+ }
381+
261382static void cmd_mode (CommandContext& ctx, const std::string& raw_args) {
262383 const std::string args = trim_ascii_command (raw_args);
263384 if (args.empty ()) {
@@ -1510,6 +1631,7 @@ void register_builtin_commands(CommandRegistry& registry) {
15101631 register_proxy_command (registry);
15111632 register_websearch_command (registry);
15121633 register_remote_control_command (registry);
1634+ registry.register_command ({" feedback" , " Upload current session diagnostics to the configured upgrade service" , cmd_feedback});
15131635 registry.register_command ({" browser" , " Show or toggle ACE Browser Bridge tools for this session" , cmd_browser});
15141636 registry.register_command ({" title" , " Set or show the window title for this session" , cmd_title});
15151637 registry.register_command ({" page-step" , " Toggle single-line PgUp/PgDn scrolling (for terminals that swallow Alt+Arrow)" , cmd_page_step});
0 commit comments