Skip to content

Commit 81d0262

Browse files
committed
feat(feedback): implement feedback upload functionality and related tests
1 parent 599f1f2 commit 81d0262

5 files changed

Lines changed: 840 additions & 0 deletions

File tree

src/commands/builtin_commands.cpp

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
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"
@@ -27,9 +28,13 @@
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>
@@ -40,6 +45,8 @@
4045

4146
namespace acecode {
4247

48+
namespace fs = std::filesystem;
49+
4350
namespace {
4451

4552
std::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+
<< "\nUpload URL: " << upload_url;
291+
if (!package_path.empty()) {
292+
oss << "\nPackage 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+
261382
static 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

Comments
 (0)