diff --git a/src/agent/Spinner.zig b/src/agent/Spinner.zig index 1d949b2c..ac1a6960 100644 --- a/src/agent/Spinner.zig +++ b/src/agent/Spinner.zig @@ -109,32 +109,14 @@ pub fn stop(self: *Self) void { self.last_render_len = 0; } -/// End a turn with no commit (used on hard API errors, where the caller will -/// surface the error itself). +/// End a turn with no commit. The caller is responsible for surfacing the +/// outcome — tool results, error messages, or summaries. pub fn cancel(self: *Self) void { if (!self.enabled) return; self.mu.lock(); defer self.mu.unlock(); if (self.state == .idle) return; - // Manual command success → commit a green `●` line so the user sees a - // permanent "done" confirmation. Errors and agent-side cancels just clear. - if (std.meta.activeTag(self.state) == .tool and self.state.tool.manual and !self.state.tool.failed) { - const tool = self.state.tool; - var buf: [frame_buf_bytes]u8 = undefined; - const line = std.fmt.bufPrint( - &buf, - "\r" ++ ansi.green ++ "●" ++ ansi.reset ++ " " ++ ansi.dim ++ "[{s} {s}]" ++ ansi.reset ++ clear_eol ++ "\n", - .{ tool.name_buf[0..tool.name_len], tool.args_buf[0..tool.args_len] }, - ) catch { - _ = std.posix.write(std.posix.STDERR_FILENO, "\r" ++ clear_eol) catch {}; - self.state = .idle; - self.last_render_len = 0; - return; - }; - _ = std.posix.write(std.posix.STDERR_FILENO, line) catch {}; - } else { - _ = std.posix.write(std.posix.STDERR_FILENO, "\r" ++ clear_eol) catch {}; - } + _ = std.posix.write(std.posix.STDERR_FILENO, "\r" ++ clear_eol) catch {}; self.state = .idle; self.last_render_len = 0; } diff --git a/src/agent/Terminal.zig b/src/agent/Terminal.zig index c0efb51f..d732310f 100644 --- a/src/agent/Terminal.zig +++ b/src/agent/Terminal.zig @@ -599,7 +599,7 @@ pub fn printToolResult(self: *Self, name: []const u8, result: []const u8) void { if (!self.isRepl() and !atLeast(self.verbosity, .high)) return; if (self.repl_arena) |*a| { defer _ = a.reset(.retain_capacity); - const bytes = formatReplResult(a.allocator(), name, result) catch return; + const bytes = formatReplResult(a.allocator(), result) catch return; if (self.spinner.emitAbove(bytes)) return; _ = std.posix.write(std.posix.STDERR_FILENO, bytes) catch {}; return; @@ -609,10 +609,10 @@ pub fn printToolResult(self: *Self, name: []const u8, result: []const u8) void { std.debug.print("{s}{s}[result: {s}]{s} {s}{s}\n", .{ ansi.dim, ansi.green, name, ansi.reset, truncated, ellipsis }); } -/// REPL output: header + body, pretty-print JSON if parseable, raw otherwise. +/// REPL output: green-dot marker followed by the body, pretty-printed if JSON. /// Builds the entire payload in the arena so callers can route it past the /// spinner (`emitAbove`) without interleaving with frame writes. -fn formatReplResult(arena: std.mem.Allocator, name: []const u8, result: []const u8) ![]const u8 { +fn formatReplResult(arena: std.mem.Allocator, result: []const u8) ![]const u8 { var aw: std.Io.Writer.Allocating = .init(arena); const w = &aw.writer; @@ -626,7 +626,7 @@ fn formatReplResult(arena: std.mem.Allocator, name: []const u8, result: []const else null; const sep: []const u8 = if (parsed != null) "\n" else " "; - try w.print("{s}{s}[result: {s}]{s}{s}", .{ ansi.dim, ansi.green, name, ansi.reset, sep }); + try w.print("{s}●{s}{s}", .{ ansi.green, ansi.reset, sep }); if (parsed) |v| { std.json.Stringify.value(v, .{ .whitespace = .indent_2 }, w) catch { try w.writeAll(result); @@ -642,7 +642,16 @@ pub fn printError(self: *Self, msg: []const u8) void { self.printErrorFmt("{s}", .{msg}); } -pub fn printErrorFmt(_: *Self, comptime fmt: []const u8, args: anytype) void { +pub fn printErrorFmt(self: *Self, comptime fmt: []const u8, args: anytype) void { + if (self.repl_arena) |*a| { + defer _ = a.reset(.retain_capacity); + var aw: std.Io.Writer.Allocating = .init(a.allocator()); + aw.writer.print("{s}●{s} " ++ fmt ++ "\n", .{ ansi.red, ansi.reset } ++ args) catch return; + const bytes = aw.written(); + if (self.spinner.emitAbove(bytes)) return; + _ = std.posix.write(std.posix.STDERR_FILENO, bytes) catch {}; + return; + } std.debug.print("{s}{s}Error: " ++ fmt ++ "{s}\n", .{ ansi.bold, ansi.red } ++ args ++ .{ansi.reset}); }