const std = @import("std");const debug_trace = @import("../shared/debug_trace.zig");const command_contract = @import("../execution/command_contract.zig");const io_mod = @import("../shared/io.zig");const types = @import("../shared/types.zig");const tool_contracts = @import("../agent/runtime/tool_contracts.zig");const background_launch_identity = @import("../background/background_launch_identity.zig");const background_runtime = @import("../background/background_runtime.zig");const tool_result_errors = @import("tool_result_errors.zig"); const Allocator = std.mem.Allocator;const ToolExecutionResult = tool_contracts.ToolExecutionResult; pub const Foreground = struct { pub fn cancelledFailure( arena: Allocator, result: command_contract.RunCommandResult, ) !?ToolExecutionResult { if (!result.cancelled) return null; const command_result_json = if (result.command_result) |command_result| command_result.toJson(arena) catch |err| { debug_trace.logf("tool", "cancelled command result metadata omitted err={s}", .{@errorName(err)}); return error.Cancelled; } else null; return .{ .status = .failure, .cancelled = true, .model_output = "command cancelled\n", .command_result_json = command_result_json, }; } pub fn nonZeroFailure( arena: Allocator, result: command_contract.RunCommandResult, ) !?ToolExecutionResult { const command_result = result.command_result orelse return null; const foreground = switch (command_result) { .foreground => |foreground| foreground, .background => return null, }; if ((foreground.exit_code == null or foreground.exit_code.? == 0) and foreground.signal == null and !foreground.timed_out) return null; var details: [6]tool_result_errors.Detail = undefined; var count: usize = 0; details[count] = .{ .name = "command", .value = .{ .string = foreground.command } }; count += 1; details[count] = .{ .name = "cwd", .value = .{ .string = foreground.cwd } }; count += 1; if (foreground.exit_code) |code| { details[count] = .{ .name = "exit_code", .value = .{ .integer = code } }; count += 1; } if (foreground.signal) |signal| { details[count] = .{ .name = "signal", .value = .{ .unsigned = signal } }; count += 1; } const stderr_text = extractEnvelope(result.output, "<stderr>\n", "\n</stderr>"); if (stderr_text.len > 0) { details[count] = .{ .name = "stderr", .value = .{ .string = stderr_text } }; count += 1; } return .{ .status = .failure, .model_output = try tool_result_errors.toolExecutionFailureJson(arena, .{ .tool_name = "terminal", .message = if (foreground.exit_code != null) "Command exited with non-zero status" else "Command terminated before completing successfully", .details = details[0..count], .suggestion = "Inspect stderr and the command context, then fix the command or explain the blocker rather than retrying unchanged.", }), .command_result_json = try command_result.toJson(arena), }; } pub fn timeoutFailure( arena: Allocator, command: []const u8, cwd: []const u8, timeout_ms: ?usize, started_ms: ?i64, ) !ToolExecutionResult { const output = if (timeout_ms) |ms| try std.fmt.allocPrint(arena, "timeout=true\ntimeout_ms={d}\ncommand timed out and was terminated\n", .{ms}) else try arena.dupe(u8, "timeout=true\ncommand timed out and was terminated\n"); return .{ .status = .failure, .model_output = output, .command_result_json = try (command_contract.CommandResult{ .foreground = .{ .command = command, .cwd = cwd, .timed_out = true, .duration_ms = if (started_ms) |started| elapsedMs(started, io_mod.milliTimestamp()) else null, } }).toJson(arena), }; }}; pub const Sandbox = struct { pub fn retryUnavailableFailure( arena: Allocator, command: []const u8, cwd: []const u8, result: command_contract.RunCommandResult, ) !ToolExecutionResult { const output = try std.fmt.allocPrint( arena, "sandbox_denied=true\n" ++ "mode=headless\n" ++ "sandbox_retry_available=false\n" ++ "reason=no_permission_ui\n" ++ "permission=sandbox\n" ++ "message=OS sandbox denied the command. Broader file access cannot be requested in noninteractive mode; rerun in the interactive shell to approve a sandbox retry or adjust sandbox configuration.\n" ++ "original_result:\n{s}", .{result.output}, ); return .{ .status = .failure, .model_output = output, .command_result_json = try deniedCommandResult(command, cwd, result.command_result).toJson(arena), }; } pub fn preflightUnavailableFailure( arena: Allocator, command: []const u8, cwd: []const u8, ) !ToolExecutionResult { const output = try arena.dupe( u8, "sandbox_permission_required=true\n" ++ "mode=headless\n" ++ "sandbox_retry_available=false\n" ++ "reason=no_permission_ui\n" ++ "permission=sandbox\n" ++ "message=This command needs broader file access before running. Noninteractive mode cannot ask for that sandbox approval; rerun in the interactive shell to approve it or adjust sandbox configuration.\n", ); return .{ .status = .failure, .model_output = output, .command_result_json = try deniedCommandResult(command, cwd, null).toJson(arena), }; }}; pub const Background = struct { pub fn persistenceUnavailableFailure(arena: Allocator) !ToolExecutionResult { const output = try arena.dupe( u8, "background_persistence_required=true\n" ++ "background_persistence_available=false\n" ++ "mode=headless\n" ++ "reason=session_store_unavailable\n" ++ "message=headless background commands require session persistence so they can be inspected after fx ask exits. Remove --no-save or restore access to the session store, then retry.\n", ); return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output }; } pub fn launchPreparationFailure(arena: Allocator, err: anyerror) !ToolExecutionResult { const output = try std.fmt.allocPrint( arena, "background_launch_failed=true\n" ++ "error={s}\n" ++ "message=background launch preparation failed before a job was started.\n", .{@errorName(err)}, ); return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output, .interactive_notice = .{ .topic = "background", .tone = .@"error", .body = try std.fmt.allocPrint( arena, "Command launch preparation failed ({s}).", .{@errorName(err)}, ), }, }; } pub fn persistenceSaveFailure( arena: Allocator, err: anyerror, launch_identity_fields: []const u8, ) !ToolExecutionResult { const identity_fields = switch (err) { error.BackgroundTerminationIndeterminate, error.BackgroundProcessIdentityIndeterminate, => launch_identity_fields, else => "", }; const details = switch (err) { error.BackgroundPersistenceRequired => "background_persistence_required=true\n" ++ "background_started=true\n" ++ "background_stopped=true\n" ++ "reason=metadata_persist_failed\n" ++ "message=headless background command metadata could not be confirmed, so the launched job was stopped instead of being reported as manageable.\n", error.BackgroundTerminationIndeterminate => "background_termination_indeterminate=true\n" ++ "background_started=true\n" ++ "background_stopped=unknown\n" ++ "reason=termination_unconfirmed\n" ++ "message=headless background command metadata could not be confirmed and termination could not be confirmed; the job may still be running.\n", error.BackgroundProcessIdentityIndeterminate => "background_process_identity_indeterminate=true\n" ++ "background_command_released=false\n" ++ "background_stopped=unknown\n" ++ "reason=wrapper_cleanup_unconfirmed\n" ++ "message=background wrapper cleanup could not be confirmed; the command was not reported as released and the wrapper process may still exist.\n", else => "background_launch_failed=true\n" ++ "background_started=false\n" ++ "reason=launch_failed\n" ++ "message=background launch failed before a manageable job was confirmed.\n", }; const output = try std.fmt.allocPrint( arena, "mode=headless\n" ++ "error={s}\n" ++ "{s}" ++ "{s}", .{ @errorName(err), identity_fields, details }, ); const interactive_body = switch (err) { error.BackgroundPersistenceRequired => try arena.dupe( u8, "Command metadata could not be saved; the launched job was stopped.", ), error.BackgroundTerminationIndeterminate => try arena.dupe( u8, "Command metadata could not be saved; whether the launched job stopped could not be confirmed.", ), error.BackgroundProcessIdentityIndeterminate => try arena.dupe( u8, "Command cleanup could not be confirmed; the wrapper process may still exist.", ), else => try std.fmt.allocPrint( arena, "Command launch failed before a manageable job was confirmed ({s}).", .{@errorName(err)}, ), }; return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output, .interactive_notice = .{ .topic = "background", .tone = .@"error", .body = interactive_body, }, }; } pub fn launchFailure(arena: Allocator, err: anyerror) !ToolExecutionResult { return .{ .model_output = try std.fmt.allocPrint( arena, "background launch failed\nreason={s}", .{@errorName(err)}, ) }; } pub fn fromTaskSnapshot( arena: Allocator, task: background_runtime.TaskSnapshot, ) !command_contract.BackgroundCommand { return .{ .pid = try arena.dupe(u8, task.pid), .command = try arena.dupe(u8, task.command), .cwd = try arena.dupe(u8, task.cwd), .log_path = try arena.dupe(u8, task.log_path), .url = if (task.server_url) |url| try arena.dupe(u8, url) else null, .expect_url = task.expect_url, }; } pub fn taskCommandResultJson( arena: Allocator, task: background_runtime.TaskSnapshot, state: []const u8, ) ![]const u8 { return (command_contract.CommandResult{ .background = .{ .command = task.command, .cwd = task.cwd, .background_id = task.id, .pid = task.pid, .log_path = task.log_path, .state = state, .server_url = task.server_url, } }).toJson(arena); } pub fn commandResultJson( arena: Allocator, background: command_contract.BackgroundCommand, background_id: u64, state: []const u8, ) ![]const u8 { return (command_contract.CommandResult{ .background = .{ .command = background.command, .cwd = background.cwd, .background_id = background_id, .pid = background.pid, .log_path = background.log_path, .state = state, .server_url = background.url, } }).toJson(arena); } pub fn formatReuseOutput(arena: Allocator, task: background_runtime.TaskSnapshot) ![]const u8 { var out: std.Io.Writer.Allocating = .init(arena); defer out.deinit(); try out.writer.writeAll("reused existing background command\n"); try out.writer.print("id={d}\n", .{task.id}); try out.writer.print("pid={s}\n", .{task.pid}); try out.writer.print("log={s}\n", .{task.log_path}); if (task.server_url) |url| { try out.writer.print("url={s}\n", .{url}); } return try out.toOwnedSlice(); } pub fn formatReuseNotice(arena: Allocator, task: background_runtime.TaskSnapshot) ![]const u8 { if (task.server_url) |url| { return std.fmt.allocPrint(arena, "Background #{d}: reusing running server at {s}", .{ task.id, url }); } if (task.expect_url) { return std.fmt.allocPrint(arena, "Background #{d}: reusing running server. Waiting for local URL.", .{task.id}); } return std.fmt.allocPrint(arena, "Background #{d}: reusing running command. Log: {s}", .{ task.id, task.log_path }); } pub fn interactiveReuseNotice(arena: Allocator, task: background_runtime.TaskSnapshot) !types.SemanticNotice { const body = if (task.server_url) |url| try std.fmt.allocPrint(arena, "Command #{d} reused. Server: {s}.", .{ task.id, url }) else if (task.expect_url) try std.fmt.allocPrint(arena, "Command #{d} reused. Waiting for local URL.", .{task.id}) else try std.fmt.allocPrint(arena, "Command #{d} reused. Log: {s}", .{ task.id, task.log_path }); return .{ .topic = "background", .tone = .neutral, .body = body, }; }}; fn extractEnvelope(output: []const u8, open: []const u8, close: []const u8) []const u8 { const start = std.mem.find(u8, output, open) orelse return ""; const body = output[start + open.len ..]; const end = std.mem.find(u8, body, close) orelse return ""; return body[0..end];} fn deniedCommandResult( command: []const u8, cwd: []const u8, result: ?command_contract.CommandResult,) command_contract.CommandResult { const existing = result orelse return .{ .foreground = .{ .command = command, .cwd = cwd, .sandbox_denied = true, } }; return switch (existing) { .foreground => |foreground| blk: { var denied = foreground; denied.sandbox_denied = true; break :blk .{ .foreground = denied }; }, .background => existing, };} fn elapsedMs(started_ms: i64, finished_ms: i64) u64 { return if (finished_ms > started_ms) @intCast(finished_ms - started_ms) else 0;} fn expectContains(haystack: []const u8, needle: []const u8) !void { try std.testing.expect(std.mem.find(u8, haystack, needle) != null);} fn freeBackgroundCommand(alloc: Allocator, background: command_contract.BackgroundCommand) void { alloc.free(background.pid); alloc.free(background.command); alloc.free(background.cwd); alloc.free(background.log_path); if (background.url) |url| alloc.free(url);} test "command result mapping preserves non-zero stderr envelope and JSON" { const alloc = std.testing.allocator; const result = try Foreground.nonZeroFailure(alloc, .{ .output = "<stderr>\nbad [redacted]\n</stderr>", .command_result = .{ .foreground = .{ .command = "printf bad >&2; exit 7", .cwd = "/tmp/workspace", .exit_code = 7, .stderr_bytes = 15, } }, }) orelse return error.TestExpectedEqual; defer alloc.free(result.model_output); defer alloc.free(result.command_result_json.?); try expectContains(result.model_output, "Command exited with non-zero status"); try expectContains(result.model_output, "\"stderr\":\"bad [redacted]\""); try expectContains(result.command_result_json.?, "\"kind\":\"foreground\""); try expectContains(result.command_result_json.?, "\"exit_code\":7");} test "cancelled command mapping survives metadata serialization failure" { var failing = std.testing.FailingAllocator.init( std.testing.allocator, .{ .fail_index = 0 }, ); try std.testing.expectError( error.Cancelled, Foreground.cancelledFailure(failing.allocator(), .{ .output = "ignored", .cancelled = true, .command_result = .{ .foreground = .{ .command = "sleep 5", .cwd = "/tmp", } }, }), );} test "command result mapping preserves timeout and headless sandbox JSON" { const alloc = std.testing.allocator; const timeout = try Foreground.timeoutFailure( alloc, "sleep 5", "/tmp/workspace", 5, null, ); defer alloc.free(timeout.model_output); defer alloc.free(timeout.command_result_json.?); try std.testing.expectEqualStrings( "timeout=true\ntimeout_ms=5\ncommand timed out and was terminated\n", timeout.model_output, ); try expectContains(timeout.command_result_json.?, "\"timed_out\":true"); const sandbox = try Sandbox.retryUnavailableFailure(alloc, "npm test", "/tmp/workspace", .{ .output = "Operation not permitted", .command_result = .{ .foreground = .{ .command = "npm test", .cwd = "/tmp/workspace", .exit_code = 1, } }, }); defer alloc.free(sandbox.model_output); defer alloc.free(sandbox.command_result_json.?); try expectContains(sandbox.model_output, "sandbox_retry_available=false\n"); try expectContains(sandbox.command_result_json.?, "\"sandbox_denied\":true");} test "command result mapping projects background reuse output and JSON" { const alloc = std.testing.allocator; var pid = [_]u8{ '4', '2' }; var command = [_]u8{ 'n', 'p', 'm', ' ', 'r', 'u', 'n', ' ', 'd', 'e', 'v' }; var cwd = [_]u8{ '/', 't', 'm', 'p' }; var log_path = [_]u8{ '/', 't', 'm', 'p', '/', 's', 'e', 'r', 'v', 'e', 'r', '.', 'l', 'o', 'g' }; var url = [_]u8{ 'h', 't', 't', 'p', ':', '/', '/', 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', ':', '4', '2', '0', '0' }; const task = background_runtime.TaskSnapshot{ .id = 9, .pid = pid[0..], .command = command[0..], .cwd = cwd[0..], .log_path = log_path[0..], .expect_url = true, .server_url = url[0..], .started_at_ms = 0, .state = .running, }; const background = try Background.fromTaskSnapshot(alloc, task); defer freeBackgroundCommand(alloc, background); const output = try Background.formatReuseOutput(alloc, task); defer alloc.free(output); const notice = try Background.formatReuseNotice(alloc, task); defer alloc.free(notice); const interactive_notice = try Background.interactiveReuseNotice(alloc, task); defer alloc.free(interactive_notice.body); const json = try Background.taskCommandResultJson(alloc, task, "running"); defer alloc.free(json); const started_json = try Background.commandResultJson(alloc, background, 10, "running"); defer alloc.free(started_json); try expectContains(output, "reused existing background command\n"); try expectContains(output, "url=http://localhost:4200\n");Lines 1–500 of 620. Load more
const std = @import("std"); const debug_trace = @import("../shared/debug_trace.zig"); const command_contract = @import("../execution/command_contract.zig"); const io_mod = @import("../shared/io.zig"); const types = @import("../shared/types.zig"); const tool_contracts = @import("../agent/runtime/tool_contracts.zig"); const background_launch_identity = @import("../background/background_launch_identity.zig"); const background_runtime = @import("../background/background_runtime.zig"); const tool_result_errors = @import("tool_result_errors.zig"); const Allocator = std.mem.Allocator; const ToolExecutionResult = tool_contracts.ToolExecutionResult; pub const Foreground = struct { pub fn cancelledFailure( arena: Allocator, result: command_contract.RunCommandResult, ) !?ToolExecutionResult { if (!result.cancelled) return null; const command_result_json = if (result.command_result) |command_result| command_result.toJson(arena) catch |err| { debug_trace.logf("tool", "cancelled command result metadata omitted err={s}", .{@errorName(err)}); return error.Cancelled; } else null; return .{ .status = .failure, .cancelled = true, .model_output = "command cancelled\n", .command_result_json = command_result_json, }; } pub fn nonZeroFailure( arena: Allocator, result: command_contract.RunCommandResult, ) !?ToolExecutionResult { const command_result = result.command_result orelse return null; const foreground = switch (command_result) { .foreground => |foreground| foreground, .background => return null, }; if ((foreground.exit_code == null or foreground.exit_code.? == 0) and foreground.signal == null and !foreground.timed_out) return null; var details: [6]tool_result_errors.Detail = undefined; var count: usize = 0; details[count] = .{ .name = "command", .value = .{ .string = foreground.command } }; count += 1; details[count] = .{ .name = "cwd", .value = .{ .string = foreground.cwd } }; count += 1; if (foreground.exit_code) |code| { details[count] = .{ .name = "exit_code", .value = .{ .integer = code } }; count += 1; } if (foreground.signal) |signal| { details[count] = .{ .name = "signal", .value = .{ .unsigned = signal } }; count += 1; } const stderr_text = extractEnvelope(result.output, "<stderr>\n", "\n</stderr>"); if (stderr_text.len > 0) { details[count] = .{ .name = "stderr", .value = .{ .string = stderr_text } }; count += 1; } return .{ .status = .failure, .model_output = try tool_result_errors.toolExecutionFailureJson(arena, .{ .tool_name = "terminal", .message = if (foreground.exit_code != null) "Command exited with non-zero status" else "Command terminated before completing successfully", .details = details[0..count], .suggestion = "Inspect stderr and the command context, then fix the command or explain the blocker rather than retrying unchanged.", }), .command_result_json = try command_result.toJson(arena), }; } pub fn timeoutFailure( arena: Allocator, command: []const u8, cwd: []const u8, timeout_ms: ?usize, started_ms: ?i64, ) !ToolExecutionResult { const output = if (timeout_ms) |ms| try std.fmt.allocPrint(arena, "timeout=true\ntimeout_ms={d}\ncommand timed out and was terminated\n", .{ms}) else try arena.dupe(u8, "timeout=true\ncommand timed out and was terminated\n"); return .{ .status = .failure, .model_output = output, .command_result_json = try (command_contract.CommandResult{ .foreground = .{ .command = command, .cwd = cwd, .timed_out = true, .duration_ms = if (started_ms) |started| elapsedMs(started, io_mod.milliTimestamp()) else null, } }).toJson(arena), }; } }; pub const Sandbox = struct { pub fn retryUnavailableFailure( arena: Allocator, command: []const u8, cwd: []const u8, result: command_contract.RunCommandResult, ) !ToolExecutionResult { const output = try std.fmt.allocPrint( arena, "sandbox_denied=true\n" ++ "mode=headless\n" ++ "sandbox_retry_available=false\n" ++ "reason=no_permission_ui\n" ++ "permission=sandbox\n" ++ "message=OS sandbox denied the command. Broader file access cannot be requested in noninteractive mode; rerun in the interactive shell to approve a sandbox retry or adjust sandbox configuration.\n" ++ "original_result:\n{s}", .{result.output}, ); return .{ .status = .failure, .model_output = output, .command_result_json = try deniedCommandResult(command, cwd, result.command_result).toJson(arena), }; } pub fn preflightUnavailableFailure( arena: Allocator, command: []const u8, cwd: []const u8, ) !ToolExecutionResult { const output = try arena.dupe( u8, "sandbox_permission_required=true\n" ++ "mode=headless\n" ++ "sandbox_retry_available=false\n" ++ "reason=no_permission_ui\n" ++ "permission=sandbox\n" ++ "message=This command needs broader file access before running. Noninteractive mode cannot ask for that sandbox approval; rerun in the interactive shell to approve it or adjust sandbox configuration.\n", ); return .{ .status = .failure, .model_output = output, .command_result_json = try deniedCommandResult(command, cwd, null).toJson(arena), }; } }; pub const Background = struct { pub fn persistenceUnavailableFailure(arena: Allocator) !ToolExecutionResult { const output = try arena.dupe( u8, "background_persistence_required=true\n" ++ "background_persistence_available=false\n" ++ "mode=headless\n" ++ "reason=session_store_unavailable\n" ++ "message=headless background commands require session persistence so they can be inspected after fx ask exits. Remove --no-save or restore access to the session store, then retry.\n", ); return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output }; } pub fn launchPreparationFailure(arena: Allocator, err: anyerror) !ToolExecutionResult { const output = try std.fmt.allocPrint( arena, "background_launch_failed=true\n" ++ "error={s}\n" ++ "message=background launch preparation failed before a job was started.\n", .{@errorName(err)}, ); return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output, .interactive_notice = .{ .topic = "background", .tone = .@"error", .body = try std.fmt.allocPrint( arena, "Command launch preparation failed ({s}).", .{@errorName(err)}, ), }, }; } pub fn persistenceSaveFailure( arena: Allocator, err: anyerror, launch_identity_fields: []const u8, ) !ToolExecutionResult { const identity_fields = switch (err) { error.BackgroundTerminationIndeterminate, error.BackgroundProcessIdentityIndeterminate, => launch_identity_fields, else => "", }; const details = switch (err) { error.BackgroundPersistenceRequired => "background_persistence_required=true\n" ++ "background_started=true\n" ++ "background_stopped=true\n" ++ "reason=metadata_persist_failed\n" ++ "message=headless background command metadata could not be confirmed, so the launched job was stopped instead of being reported as manageable.\n", error.BackgroundTerminationIndeterminate => "background_termination_indeterminate=true\n" ++ "background_started=true\n" ++ "background_stopped=unknown\n" ++ "reason=termination_unconfirmed\n" ++ "message=headless background command metadata could not be confirmed and termination could not be confirmed; the job may still be running.\n", error.BackgroundProcessIdentityIndeterminate => "background_process_identity_indeterminate=true\n" ++ "background_command_released=false\n" ++ "background_stopped=unknown\n" ++ "reason=wrapper_cleanup_unconfirmed\n" ++ "message=background wrapper cleanup could not be confirmed; the command was not reported as released and the wrapper process may still exist.\n", else => "background_launch_failed=true\n" ++ "background_started=false\n" ++ "reason=launch_failed\n" ++ "message=background launch failed before a manageable job was confirmed.\n", }; const output = try std.fmt.allocPrint( arena, "mode=headless\n" ++ "error={s}\n" ++ "{s}" ++ "{s}", .{ @errorName(err), identity_fields, details }, ); const interactive_body = switch (err) { error.BackgroundPersistenceRequired => try arena.dupe( u8, "Command metadata could not be saved; the launched job was stopped.", ), error.BackgroundTerminationIndeterminate => try arena.dupe( u8, "Command metadata could not be saved; whether the launched job stopped could not be confirmed.", ), error.BackgroundProcessIdentityIndeterminate => try arena.dupe( u8, "Command cleanup could not be confirmed; the wrapper process may still exist.", ), else => try std.fmt.allocPrint( arena, "Command launch failed before a manageable job was confirmed ({s}).", .{@errorName(err)}, ), }; return .{ .status = .failure, .model_output = output, .finish_turn = true, .system_notice = output, .interactive_notice = .{ .topic = "background", .tone = .@"error", .body = interactive_body, }, }; } pub fn launchFailure(arena: Allocator, err: anyerror) !ToolExecutionResult { return .{ .model_output = try std.fmt.allocPrint( arena, "background launch failed\nreason={s}", .{@errorName(err)}, ) }; } pub fn fromTaskSnapshot( arena: Allocator, task: background_runtime.TaskSnapshot, ) !command_contract.BackgroundCommand { return .{ .pid = try arena.dupe(u8, task.pid), .command = try arena.dupe(u8, task.command), .cwd = try arena.dupe(u8, task.cwd), .log_path = try arena.dupe(u8, task.log_path), .url = if (task.server_url) |url| try arena.dupe(u8, url) else null, .expect_url = task.expect_url, }; } pub fn taskCommandResultJson( arena: Allocator, task: background_runtime.TaskSnapshot, state: []const u8, ) ![]const u8 { return (command_contract.CommandResult{ .background = .{ .command = task.command, .cwd = task.cwd, .background_id = task.id, .pid = task.pid, .log_path = task.log_path, .state = state, .server_url = task.server_url, } }).toJson(arena); } pub fn commandResultJson( arena: Allocator, background: command_contract.BackgroundCommand, background_id: u64, state: []const u8, ) ![]const u8 { return (command_contract.CommandResult{ .background = .{ .command = background.command, .cwd = background.cwd, .background_id = background_id, .pid = background.pid, .log_path = background.log_path, .state = state, .server_url = background.url, } }).toJson(arena); } pub fn formatReuseOutput(arena: Allocator, task: background_runtime.TaskSnapshot) ![]const u8 { var out: std.Io.Writer.Allocating = .init(arena); defer out.deinit(); try out.writer.writeAll("reused existing background command\n"); try out.writer.print("id={d}\n", .{task.id}); try out.writer.print("pid={s}\n", .{task.pid}); try out.writer.print("log={s}\n", .{task.log_path}); if (task.server_url) |url| { try out.writer.print("url={s}\n", .{url}); } return try out.toOwnedSlice(); } pub fn formatReuseNotice(arena: Allocator, task: background_runtime.TaskSnapshot) ![]const u8 { if (task.server_url) |url| { return std.fmt.allocPrint(arena, "Background #{d}: reusing running server at {s}", .{ task.id, url }); } if (task.expect_url) { return std.fmt.allocPrint(arena, "Background #{d}: reusing running server. Waiting for local URL.", .{task.id}); } return std.fmt.allocPrint(arena, "Background #{d}: reusing running command. Log: {s}", .{ task.id, task.log_path }); } pub fn interactiveReuseNotice(arena: Allocator, task: background_runtime.TaskSnapshot) !types.SemanticNotice { const body = if (task.server_url) |url| try std.fmt.allocPrint(arena, "Command #{d} reused. Server: {s}.", .{ task.id, url }) else if (task.expect_url) try std.fmt.allocPrint(arena, "Command #{d} reused. Waiting for local URL.", .{task.id}) else try std.fmt.allocPrint(arena, "Command #{d} reused. Log: {s}", .{ task.id, task.log_path }); return .{ .topic = "background", .tone = .neutral, .body = body, }; } }; fn extractEnvelope(output: []const u8, open: []const u8, close: []const u8) []const u8 { const start = std.mem.find(u8, output, open) orelse return ""; const body = output[start + open.len ..]; const end = std.mem.find(u8, body, close) orelse return ""; return body[0..end]; } fn deniedCommandResult( command: []const u8, cwd: []const u8, result: ?command_contract.CommandResult, ) command_contract.CommandResult { const existing = result orelse return .{ .foreground = .{ .command = command, .cwd = cwd, .sandbox_denied = true, } }; return switch (existing) { .foreground => |foreground| blk: { var denied = foreground; denied.sandbox_denied = true; break :blk .{ .foreground = denied }; }, .background => existing, }; } fn elapsedMs(started_ms: i64, finished_ms: i64) u64 { return if (finished_ms > started_ms) @intCast(finished_ms - started_ms) else 0; } fn expectContains(haystack: []const u8, needle: []const u8) !void { try std.testing.expect(std.mem.find(u8, haystack, needle) != null); } fn freeBackgroundCommand(alloc: Allocator, background: command_contract.BackgroundCommand) void { alloc.free(background.pid); alloc.free(background.command); alloc.free(background.cwd); alloc.free(background.log_path); if (background.url) |url| alloc.free(url); } test "command result mapping preserves non-zero stderr envelope and JSON" { const alloc = std.testing.allocator; const result = try Foreground.nonZeroFailure(alloc, .{ .output = "<stderr>\nbad [redacted]\n</stderr>", .command_result = .{ .foreground = .{ .command = "printf bad >&2; exit 7", .cwd = "/tmp/workspace", .exit_code = 7, .stderr_bytes = 15, } }, }) orelse return error.TestExpectedEqual; defer alloc.free(result.model_output); defer alloc.free(result.command_result_json.?); try expectContains(result.model_output, "Command exited with non-zero status"); try expectContains(result.model_output, "\"stderr\":\"bad [redacted]\""); try expectContains(result.command_result_json.?, "\"kind\":\"foreground\""); try expectContains(result.command_result_json.?, "\"exit_code\":7"); } test "cancelled command mapping survives metadata serialization failure" { var failing = std.testing.FailingAllocator.init( std.testing.allocator, .{ .fail_index = 0 }, ); try std.testing.expectError( error.Cancelled, Foreground.cancelledFailure(failing.allocator(), .{ .output = "ignored", .cancelled = true, .command_result = .{ .foreground = .{ .command = "sleep 5", .cwd = "/tmp", } }, }), ); } test "command result mapping preserves timeout and headless sandbox JSON" { const alloc = std.testing.allocator; const timeout = try Foreground.timeoutFailure( alloc, "sleep 5", "/tmp/workspace", 5, null, ); defer alloc.free(timeout.model_output); defer alloc.free(timeout.command_result_json.?); try std.testing.expectEqualStrings( "timeout=true\ntimeout_ms=5\ncommand timed out and was terminated\n", timeout.model_output, ); try expectContains(timeout.command_result_json.?, "\"timed_out\":true"); const sandbox = try Sandbox.retryUnavailableFailure(alloc, "npm test", "/tmp/workspace", .{ .output = "Operation not permitted", .command_result = .{ .foreground = .{ .command = "npm test", .cwd = "/tmp/workspace", .exit_code = 1, } }, }); defer alloc.free(sandbox.model_output); defer alloc.free(sandbox.command_result_json.?); try expectContains(sandbox.model_output, "sandbox_retry_available=false\n"); try expectContains(sandbox.command_result_json.?, "\"sandbox_denied\":true"); } test "command result mapping projects background reuse output and JSON" { const alloc = std.testing.allocator; var pid = [_]u8{ '4', '2' }; var command = [_]u8{ 'n', 'p', 'm', ' ', 'r', 'u', 'n', ' ', 'd', 'e', 'v' }; var cwd = [_]u8{ '/', 't', 'm', 'p' }; var log_path = [_]u8{ '/', 't', 'm', 'p', '/', 's', 'e', 'r', 'v', 'e', 'r', '.', 'l', 'o', 'g' }; var url = [_]u8{ 'h', 't', 't', 'p', ':', '/', '/', 'l', 'o', 'c', 'a', 'l', 'h', 'o', 's', 't', ':', '4', '2', '0', '0' }; const task = background_runtime.TaskSnapshot{ .id = 9, .pid = pid[0..], .command = command[0..], .cwd = cwd[0..], .log_path = log_path[0..], .expect_url = true, .server_url = url[0..], .started_at_ms = 0, .state = .running, }; const background = try Background.fromTaskSnapshot(alloc, task); defer freeBackgroundCommand(alloc, background); const output = try Background.formatReuseOutput(alloc, task); defer alloc.free(output); const notice = try Background.formatReuseNotice(alloc, task); defer alloc.free(notice); const interactive_notice = try Background.interactiveReuseNotice(alloc, task); defer alloc.free(interactive_notice.body); const json = try Background.taskCommandResultJson(alloc, task, "running"); defer alloc.free(json); const started_json = try Background.commandResultJson(alloc, background, 10, "running"); defer alloc.free(started_json); try expectContains(output, "reused existing background command\n"); try expectContains(output, "url=http://localhost:4200\n");