Download
64 lines 2.3 KiBGoModified 2026-10-31 raw
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
package tools

import (
	"context"
	"time"

	"blinker/internal/policy"
	"blinker/internal/sandbox"
)

// previewOpts are the answers to a preview's changes.
var previewOpts = []Option{
	{Label: "Apply the changes", Allow: true},
	{Label: "Discard them"},
	{Label: "Discard them, and tell the model why", Explain: true},
}

// preview runs an Unsafe_Bash script the user chose to preview: with its
// writes kept aside and no network, shown to the user with its output,
// and made only if they approve. The model sees the output only then.
func (t *Toolbox) preview(ctx context.Context, r Result, tool, script string, timeout time.Duration) (Result, error) {
	ran, p, err := t.runCmd(ctx, r, sandbox.Cmd{
		Mode: policy.UnsafeMode, Argv: []string{t.SB.Shell(), "-c", script},
		Timeout: timeout, Preview: true,
	})
	if err != nil {
		return previewFailed(ctx, r, err)
	}
	// Failing leaves the files in the session's dir, which goes when the
	// session ends: nothing of them reaches the project.
	defer p.Discard()
	changes, about, err := p.Describe(t.SB.Project())
	if err != nil {
		return previewFailed(ctx, r, err)
	}
	title := "Unsafe_Bash's script, run as a preview, would make these changes"
	if changes == "" && about == "" {
		title = "Unsafe_Bash's script, run as a preview, changed no files"
	}
	_, ok, err := t.ask(ctx, Question{
		Tool: tool, Title: title, Body: ran.Detail, Diff: changes, About: about,
		Opts: previewOpts, Pager: true,
	}, &r, "The user discarded what this script changed. It ran as a preview, so nothing it wrote was kept.")
	if !ok {
		return r, err
	}
	if err := t.SB.Apply(ctx, p); err != nil {
		r.Output, r.Failed, r.Detail = "The user approved the script's changes, but applying them failed: "+err.Error(), true, "failed"
		return r, nil
	}
	ran.Output += "\n[sandbox] It ran as a preview, and the user applied the files it changed."
	return ran, nil
}

// previewFailed reports a preview that failed to the user only: the error
// may name what the script made, and the model sees nothing of a preview
// the user has not approved.
func previewFailed(ctx context.Context, r Result, err error) (Result, error) {
	if ctx.Err() != nil {
		return r, ctx.Err()
	}
	r.Output, r.Failed, r.Detail = "The script ran as a preview, which failed, so nothing it wrote was kept. The user saw why.", true, "the preview failed: "+err.Error()
	return r, nil
}