From fe9786bf845f844315221e08eba793ac1d27ae9d Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 22:20:09 -0300 Subject: A restored command pane comes back finished, not as a live shell After Restore a dogfood agent's command panes were fish shells under a stale `(make) exit 0` tag. The dump's terminal record now keeps a command pane's line and status, and Restore brings it back done: what it printed, its exit, no shell and no rerun. Co-Authored-By: Claude Opus 5.5 --- src/Terminal.zig | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'src/Terminal.zig') diff --git a/src/Terminal.zig b/src/Terminal.zig index e50eb2df..76be29d0 100644 --- a/src/Terminal.zig +++ b/src/Terminal.zig @@ -659,6 +659,36 @@ test "replay restores terminal cells from the retained stream" { try std.testing.expectEqualSlices(u8, try replayBytes(pane, gpa), try replayBytes(restored, gpa)); } +test "a restored command pane comes back finished, its command not run again" { + if (comptime !enabled or pardes.isolated) return error.SkipZigTest; + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 60, .rows = 16 }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + const cmd = p.freeSlot().?; + _ = try p.newCommand(cmd, "/tmp", "make test"); + feedOutput(p, p.panes[cmd].?, "ok\r\n"); + p.update(.{ .exited = .{ .pane = @intCast(cmd), .status = 2 } }); + try dump.dumpState(p); + const restored = try dump.initFromDump(gpa, .{ .tty_only = true, .cols = 60, .rows = 16 }, p.dump_out.?); + defer restored.deinit(); + var spawns: usize = 0; + while (restored.nextEffect()) |effect| switch (effect) { + .spawn => spawns += 1, + else => {}, + }; + try std.testing.expectEqual(@as(usize, 1), spawns); // the shell, not the command + const again = for (restored.panes) |slot| { + if (slot) |pane| if (pane.command != null) break pane; + } else return error.CommandPaneLost; + try std.testing.expectEqualStrings("make test", again.command.?); + try std.testing.expect(again.command_done); + try std.testing.expectEqual(@as(?u8, 2), again.command_status); + const text = try screenTextAlloc(again, gpa); + defer gpa.free(text); + try std.testing.expect(std.mem.indexOf(u8, text, "ok") != null); +} + test "a restored terminal keeps its history above a marker and gets a shell where it was" { if (comptime !enabled or pardes.isolated) return error.SkipZigTest; const gpa = std.testing.allocator; @@ -680,6 +710,7 @@ test "a restored terminal keeps its history above a marker and gets a shell wher try std.testing.expect(spawned); const again = restored.panes[0].?; try std.testing.expectEqualStrings("bash", again.shell.?); + try std.testing.expect(again.command == null); try std.testing.expectEqual(.spawn, again.pending_command.wait); const text = try screenTextAlloc(again, gpa); defer gpa.free(text); @@ -1439,6 +1470,8 @@ pub fn dumpPane( .terminal = .{ .cwd = try arena.dupe(u8, pane.cwdSlice()), .shell = try arena.dupe(u8, pane.shell orelse ""), + .command = try arena.dupe(u8, pane.command orelse ""), + .status = pane.command_status, .stream = try arena.dupe(u8, text), .stream_b64 = &.{}, .cursor = .{ .col = 0, .row = 0 }, @@ -1489,6 +1522,8 @@ pub fn dumpPane( .terminal = .{ .cwd = try arena.dupe(u8, pane.cwdSlice()), .shell = try arena.dupe(u8, pane.shell orelse ""), + .command = try arena.dupe(u8, pane.command orelse ""), + .status = pane.command_status, .stream = stream[0..len], .stream_b64 = try dump.encodeBytes(arena, try screenVt(pane, arena)), .stream_is_screen = true, -- cgit v1.3