Files
zero/internal/cli/exec_sessions.go
KRATOSandClaude Opus 4.8 80d817045d Zenline TUI + runtime-core reliability + session file checkpoints
Local development snapshot of one session's intertwined workstreams. Committed together because changes span shared files (provider.go, model.go, loop.go, types.go, streamjson) and can't be cleanly split without interactive hunk staging. Build + vet + full -race suite green.

Zenline TUI skin: Zen home / Statusline chat / permission-modal surfaces, 5 themes (Phosphor/Cyan/Sage/Violet/Mono), boot splash, mouse support, and a headless --snapshot mode (internal/zenline, cli/zenline.go, tui/zenline_view.go).

OpenAI provider: streaming tool-call accumulation hardening plus an idle-timeout watchdog (reader goroutine + context cancel) so a stalled upstream surfaces an error instead of hanging the agent forever.

Agent reliability: update_plan schema makes id optional/auto-numbered with an advertised item schema; malformed (nameless) tool calls feed a retry to the model instead of silently ending the turn; secret scrubbing at the registry boundary (covers agent loop AND MCP); structured ToolResult (changedFiles/display/redacted) surfaced via stream-json.

Session file checkpoints + safe rewind: before-mutation content snapshots (content-addressed, deduped, size-capped, 0600) captured on write/edit/apply_patch in TUI and headless; RestoreToSequence + ApplyRewind (path-traversal-guarded, session-locked); /rewind in TUI and 'zero sessions rewind' headless; stream-json checkpoint/restore events.

Design specs under docs/superpowers/specs/.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-06-06 21:41:38 +05:30

104 lines
2.8 KiB
Go

package cli
import (
"encoding/json"
"strings"
"github.com/Gitlawb/zero/internal/agent"
"github.com/Gitlawb/zero/internal/sessions"
"github.com/Gitlawb/zero/internal/tools"
)
type execSessionRecorder struct {
prepared sessions.PreparedExec
err error
}
// captureCheckpoint snapshots the before-state of files a tool call will mutate.
// Best-effort: failures never affect the run. Returns the checkpoint event and
// true when one was recorded.
func (recorder *execSessionRecorder) captureCheckpoint(workspaceRoot string, call agent.ToolCall) (sessions.Event, bool) {
if recorder.prepared.Store == nil || recorder.prepared.Session.SessionID == "" {
return sessions.Event{}, false
}
var args map[string]any
if call.Arguments != "" {
_ = json.Unmarshal([]byte(call.Arguments), &args)
}
targets := tools.MutationTargets(workspaceRoot, call.Name, args)
if len(targets) == 0 {
return sessions.Event{}, false
}
event, err := recorder.prepared.Store.CaptureToolCheckpoint(recorder.prepared.Session.SessionID, workspaceRoot, call.Name, targets)
if err != nil || event.Type == "" {
return sessions.Event{}, false
}
return event, true
}
func shouldUseExecSession(options execOptions) bool {
return options.outputFormat == execOutputStreamJSON ||
options.resume != "" ||
options.resumeLatest ||
options.fork != ""
}
func preflightExecSession(options execOptions) error {
if options.resume == "" && !options.resumeLatest && options.fork == "" {
return nil
}
if (options.resume != "" || options.resumeLatest) && options.fork != "" {
return execUsageError{"Use either --resume or --fork, not both."}
}
store := sessions.NewStore(sessions.StoreOptions{})
switch {
case options.fork != "":
session, err := store.Get(options.fork)
if err != nil {
return err
}
if session == nil {
return execUsageError{"Zero session not found: " + options.fork}
}
case options.resume != "":
session, err := store.Get(options.resume)
if err != nil {
return err
}
if session == nil {
return execUsageError{"Zero session not found: " + options.resume}
}
case options.resumeLatest:
latest, err := store.Latest()
if err != nil {
return err
}
if latest == nil {
return execUsageError{"No Zero sessions available to resume."}
}
}
return nil
}
func createSessionTitle(prompt string) string {
title := strings.Join(strings.Fields(prompt), " ")
if len(title) > 80 {
title = title[:80]
}
if title == "" {
return "Zero exec session"
}
return title
}
func (recorder *execSessionRecorder) append(eventType sessions.EventType, payload any) {
if recorder.err != nil || recorder.prepared.Store == nil || recorder.prepared.Session.SessionID == "" {
return
}
_, recorder.err = recorder.prepared.Store.AppendEvent(recorder.prepared.Session.SessionID, sessions.AppendEventInput{
Type: eventType,
Payload: payload,
})
}