Skip to content

Commit 1951122

Browse files
committed
feat(verbose): add verbose mode with real-time output display
- Add OPENCODER_VERBOSE environment variable support - Display opencode stdout/stderr in real-time when verbose enabled - Add verbose logging throughout the main loop phases - Log cycle start, phase transitions, task progress, and backoff info Signed-off-by: leocavalcante <[email protected]>
1 parent 3026b24 commit 1951122

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

src/config.zig

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ pub const Config = struct {
122122
config.task_pause_seconds = std.fmt.parseInt(u64, val, 10) catch defaults.task_pause_seconds;
123123
}
124124

125+
// OPENCODER_VERBOSE
126+
if (std.posix.getenv("OPENCODER_VERBOSE")) |val| {
127+
config.verbose = std.mem.eql(u8, val, "1") or std.mem.eql(u8, val, "true") or std.mem.eql(u8, val, "yes");
128+
}
129+
125130
return config;
126131
}
127132
};
@@ -176,3 +181,8 @@ test "Config.defaults has expected values" {
176181
try std.testing.expectEqual(false, config.verbose);
177182
try std.testing.expectEqual(@as(?[]const u8, null), config.user_hint);
178183
}
184+
185+
test "verbose flag is in Config struct" {
186+
const config = Config.defaults;
187+
_ = config.verbose;
188+
}

src/executor.zig

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ pub const Executor = struct {
346346
return .failure;
347347
}
348348

349-
// Run opencode CLI and return output
349+
// Run opencode CLI and return output with real-time stdout/stderr display
350350
fn runOpencode(self: *Executor, model: []const u8, title: []const u8, prompt: []const u8) ![]u8 {
351351
var args = std.ArrayListUnmanaged([]const u8){};
352352
defer args.deinit(self.allocator);
@@ -364,9 +364,9 @@ pub const Executor = struct {
364364
var child = std.process.Child.init(args.items, self.allocator);
365365
child.cwd = null; // Use current working directory
366366

367-
// Inherit stderr for opencode output, capture stdout for result checking
368-
child.stderr_behavior = .Inherit;
367+
// Capture both stdout and stderr for real-time display
369368
child.stdout_behavior = .Pipe;
369+
child.stderr_behavior = .Pipe;
370370

371371
// Create a new process group for the child so we can kill all descendants
372372
child.pgid = 0; // 0 means child creates its own process group
@@ -381,7 +381,7 @@ pub const Executor = struct {
381381
self.current_child_pgid = null;
382382
}
383383

384-
// Read stdout
384+
// Read stdout and stderr in real-time, displaying output as it arrives
385385
var stdout_list = std.ArrayListUnmanaged(u8){};
386386

387387
if (child.stdout) |stdout| {
@@ -393,6 +393,21 @@ pub const Executor = struct {
393393
stdout_list.deinit(self.allocator);
394394
return err;
395395
};
396+
// Display output in real-time
397+
const chunk = buf[0..n];
398+
self.log.logVerbose(chunk);
399+
}
400+
}
401+
402+
// Read stderr in real-time
403+
if (child.stderr) |stderr| {
404+
var buf: [4096]u8 = undefined;
405+
while (true) {
406+
const n = stderr.read(&buf) catch break;
407+
if (n == 0) break;
408+
// Display stderr output in real-time (prefixed to distinguish from stdout)
409+
const chunk = buf[0..n];
410+
self.log.logVerbose(chunk);
396411
}
397412
}
398413

src/loop.zig

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,30 @@ pub const Loop = struct {
6262
self.log.sayFmt("Hint: {s}", .{hint});
6363
}
6464

65+
if (self.cfg.verbose) {
66+
self.log.logVerbose("Verbose mode enabled");
67+
{
68+
var buf: [64]u8 = undefined;
69+
const msg = std.fmt.bufPrint(&buf, "Backoff base: {d}s", .{self.cfg.backoff_base}) catch "";
70+
self.log.logVerbose(msg);
71+
}
72+
{
73+
var buf: [64]u8 = undefined;
74+
const msg = std.fmt.bufPrint(&buf, "Max retries: {d}", .{self.cfg.max_retries}) catch "";
75+
self.log.logVerbose(msg);
76+
}
77+
{
78+
var buf: [64]u8 = undefined;
79+
const msg = std.fmt.bufPrint(&buf, "Task pause: {d}s", .{self.cfg.task_pause_seconds}) catch "";
80+
self.log.logVerbose(msg);
81+
}
82+
}
83+
6584
self.log.say("Running continuously (Ctrl+C to stop, Ctrl+C twice to force)");
6685
self.log.say("");
6786

6887
while (!shutdown_requested) {
88+
self.log.logVerbose("Starting new cycle");
6989
self.log.say("");
7090
self.log.sayFmt("[Cycle {d}]", .{self.st.cycle});
7191
self.log.setCycle(self.st.cycle);
@@ -75,13 +95,19 @@ pub const Loop = struct {
7595

7696
// Planning phase
7797
if (!fsutil.fileExists(self.paths.current_plan) or self.st.phase == .planning) {
98+
self.log.logVerbose("Phase: planning");
7899
// Check for ideas first
79100
var idea_list = ideas.loadAllIdeas(self.paths.ideas_dir, self.allocator, self.cfg.max_file_size) catch null;
80101

81102
var result: ExecutionResult = .failure;
82103

83104
if (idea_list) |*list| {
84105
defer list.deinit();
106+
{
107+
var buf: [64]u8 = undefined;
108+
const msg = std.fmt.bufPrint(&buf, "Found {d} idea(s) in ideas queue", .{list.ideas.len}) catch "";
109+
self.log.logVerbose(msg);
110+
}
85111

86112
if (list.ideas.len == 1) {
87113
// Single idea - use it directly
@@ -113,6 +139,7 @@ pub const Loop = struct {
113139
} else {
114140
// Multiple ideas - AI selects simplest one
115141
self.log.sayFmt("[Cycle {d}] Found {d} idea(s) in queue", .{ self.st.cycle, list.ideas.len });
142+
self.log.logVerbose("Multiple ideas found, requesting AI selection");
116143

117144
// Format ideas for AI selection
118145
const formatted = ideas.formatIdeasForSelection(list.ideas, self.allocator) catch |err| {
@@ -238,6 +265,7 @@ pub const Loop = struct {
238265
if (shutdown_requested) break;
239266

240267
// Execution phase
268+
self.log.logVerbose("Phase: execution");
241269
self.log.logFmt("[Cycle {d}] Executing tasks...", .{self.st.cycle});
242270

243271
while (!shutdown_requested) {
@@ -268,6 +296,12 @@ pub const Loop = struct {
268296
self.st.current_task_num += 1;
269297
self.st.task_index += 1;
270298

299+
{
300+
var buf: [64]u8 = undefined;
301+
const msg = std.fmt.bufPrint(&buf, "Executing task {d}/{d}", .{ self.st.current_task_num, self.st.total_tasks }) catch "";
302+
self.log.logVerbose(msg);
303+
}
304+
271305
const result = self.executor.runTask(
272306
task.description,
273307
self.st.cycle,
@@ -311,6 +345,7 @@ pub const Loop = struct {
311345
self.log.logError(" Plan file may not be updated correctly");
312346
};
313347

348+
self.log.logVerbose("Task marked complete, saving state");
314349
try self.st.save(self.paths.state_file, self.allocator);
315350

316351
// Small pause between tasks
@@ -321,6 +356,7 @@ pub const Loop = struct {
321356
if (shutdown_requested) break;
322357

323358
// Evaluation phase
359+
self.log.logVerbose("Phase: evaluation");
324360
const eval_result = evaluator.evaluate(
325361
self.executor,
326362
self.paths.current_plan,
@@ -339,6 +375,7 @@ pub const Loop = struct {
339375

340376
if (eval_result == .complete) {
341377
self.log.sayFmt("[Cycle {d}] Complete, starting new cycle", .{self.st.cycle});
378+
self.log.logVerbose("Evaluation result: complete");
342379

343380
// Archive plan
344381
plan.archive(
@@ -361,6 +398,7 @@ pub const Loop = struct {
361398
self.st.current_task_num = 0;
362399
self.st.total_tasks = 0;
363400
} else {
401+
self.log.logVerbose("Evaluation result: needs_work");
364402
// Check if there are actually pending tasks
365403
if (!evaluator.hasPendingTasks(self.paths.current_plan, self.allocator, self.cfg.max_file_size)) {
366404
self.log.sayFmt("[Cycle {d}] NEEDS_WORK but no tasks, starting new cycle", .{self.st.cycle});
@@ -385,6 +423,11 @@ pub const Loop = struct {
385423
}
386424

387425
try self.st.save(self.paths.state_file, self.allocator);
426+
{
427+
var buf: [64]u8 = undefined;
428+
const msg = std.fmt.bufPrint(&buf, "Cycle {d} complete, state saved", .{self.st.cycle -| 1}) catch "";
429+
self.log.logVerbose(msg);
430+
}
388431
self.log.sayFmt("[Cycle {d}] Complete", .{self.st.cycle -| 1});
389432
}
390433

@@ -401,6 +444,11 @@ pub const Loop = struct {
401444

402445
fn backoffSleep(self: *Loop) void {
403446
const sleep_time = self.cfg.backoff_base * 2;
447+
if (self.cfg.verbose) {
448+
var buf: [64]u8 = undefined;
449+
const msg = std.fmt.bufPrint(&buf, "Backoff: sleeping for {d} seconds", .{sleep_time}) catch "Backoff sleep";
450+
self.log.logVerbose(msg);
451+
}
404452
std.Thread.sleep(@as(u64, sleep_time) * std.time.ns_per_s);
405453
}
406454
};
@@ -499,6 +547,9 @@ fn createTestPaths(allocator: Allocator) !fsutil.Paths {
499547
try std.fs.cwd().makePath(cycle_log_dir);
500548
try std.fs.cwd().makePath(history_dir);
501549

550+
const ideas_dir = try std.fs.path.join(allocator, &.{ temp_dir, "ideas" });
551+
try std.fs.cwd().makePath(ideas_dir);
552+
502553
return fsutil.Paths{
503554
.opencoder_dir = opencoder_dir,
504555
.state_file = state_file,
@@ -507,6 +558,7 @@ fn createTestPaths(allocator: Allocator) !fsutil.Paths {
507558
.cycle_log_dir = cycle_log_dir,
508559
.alerts_file = alerts_file,
509560
.history_dir = history_dir,
561+
.ideas_dir = ideas_dir,
510562
.allocator = allocator,
511563
};
512564
}

0 commit comments

Comments
 (0)