Clarify Agentbox names and invariants

Co-authored-by: codegirl007 <codegirl-007@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-08-18 06:05:46 +00:00
co-authored by codegirl007
parent 4d2fb4a733
commit e702497996
11 changed files with 35 additions and 19 deletions
+1 -1
View File
@@ -67,7 +67,7 @@ func run(ctx context.Context, args []string) error {
fmt.Fprintf(os.Stderr, "Run artifacts: %s\n", traceStore.Directory()) fmt.Fprintf(os.Stderr, "Run artifacts: %s\n", traceStore.Directory())
return err return err
} }
fmt.Printf("Replay written to:\n%s\n", traceStore.Directory()) fmt.Printf("Run artifacts written to:\n%s\n", traceStore.Directory())
return nil return nil
} }
+2
View File
@@ -76,6 +76,8 @@ func greenCentroidX(screenshot []byte) (float64, error) {
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ { for x := bounds.Min.X; x < bounds.Max.X; x++ {
red, green, blue, _ := renderedImage.At(x, y).RGBA() red, green, blue, _ := renderedImage.At(x, y).RGBA()
// Match the mover's #00ff66 square. Requiring 1,000 matching pixels
// prevents a small green UI detail from passing verification.
if green > 0xc000 && red < 0x4000 && blue < 0x8000 { if green > 0xc000 && red < 0x4000 && blue < 0x8000 {
xCoordinateSum += uint64(x) xCoordinateSum += uint64(x)
greenPixelCount++ greenPixelCount++
+3 -1
View File
@@ -87,12 +87,14 @@ func modelPrompt(task string, history []Step, observation Observation) (string,
return "", err return "", err
} }
return "You control an interactive Linux application from screenshots. " + return "You control an interactive Linux application from screenshots. " +
"Choose exactly one safe input action, or mark done when the task is complete. " + "Choose exactly one allowed input action, or mark done when the task is complete. " +
"Use logical key names such as RIGHT, ENTER, or ESCAPE. Keep waits under 5000 ms.\n" + "Use logical key names such as RIGHT, ENTER, or ESCAPE. Keep waits under 5000 ms.\n" +
string(data), nil string(data), nil
} }
func decisionSchema() map[string]any { func decisionSchema() map[string]any {
// Strict structured output requires every action property to be present,
// even when a particular action ignores most of them.
actionProperties := map[string]any{ actionProperties := map[string]any{
"type": map[string]any{ "type": map[string]any{
"type": "string", "type": "string",
+8 -8
View File
@@ -11,10 +11,10 @@ import (
) )
type manifest struct { type manifest struct {
Command string `json:"command"` Command string `json:"command"`
Args []string `json:"args"` Args []string `json:"args"`
Env map[string]string `json:"env"` EnvironmentVariables map[string]string `json:"env"`
WindowTitle string `json:"window_title"` WindowTitle string `json:"window_title"`
} }
// Resolve turns either an executable path or a directory containing // Resolve turns either an executable path or a directory containing
@@ -58,9 +58,9 @@ func Resolve(path string) (environment.Command, error) {
return environment.Command{}, errors.New("manifest command must be a regular executable file") return environment.Command{}, errors.New("manifest command must be a regular executable file")
} }
return environment.Command{ return environment.Command{
Path: commandPath, Path: commandPath,
Args: config.Args, Args: config.Args,
Env: config.Env, EnvironmentVariables: config.EnvironmentVariables,
WindowTitle: config.WindowTitle, WindowTitle: config.WindowTitle,
}, nil }, nil
} }
@@ -17,7 +17,7 @@ func (environmentBackend *Environment) Launch(
ctx context.Context, ctx context.Context,
application environment.Command, application environment.Command,
) error { ) error {
fmt.Fprintln(environmentBackend.config.Output, "Uploading build...") fmt.Fprintln(environmentBackend.config.Output, "Staging application...")
executable, err := os.Open(application.Path) executable, err := os.Open(application.Path)
if err != nil { if err != nil {
return fmt.Errorf("open application: %w", err) return fmt.Errorf("open application: %w", err)
@@ -47,7 +47,7 @@ func (environmentBackend *Environment) Launch(
fmt.Fprintln(environmentBackend.config.Output, "Launching application...") fmt.Fprintln(environmentBackend.config.Output, "Launching application...")
dockerArguments := []string{"exec", "-d"} dockerArguments := []string{"exec", "-d"}
for variableName, variableValue := range application.Env { for variableName, variableValue := range application.EnvironmentVariables {
dockerArguments = append( dockerArguments = append(
dockerArguments, dockerArguments,
"-e", "-e",
@@ -69,6 +69,8 @@ func (environmentBackend *Environment) Start(ctx context.Context) error {
); err != nil { ); err != nil {
return fmt.Errorf("start environment: %w", err) return fmt.Errorf("start environment: %w", err)
} }
// entrypoint.sh creates this file only after both Xvfb and Openbox accept
// requests. It is the readiness handshake between host and container.
if err := waitFor(ctx, 10*time.Second, func() bool { if err := waitFor(ctx, 10*time.Second, func() bool {
_, readyErr := environmentBackend.runDocker( _, readyErr := environmentBackend.runDocker(
ctx, ctx,
+6 -5
View File
@@ -8,10 +8,10 @@ import (
// Command describes a host executable and how it should start in an // Command describes a host executable and how it should start in an
// environment. Environment implementations decide how to stage the file. // environment. Environment implementations decide how to stage the file.
type Command struct { type Command struct {
Path string Path string
Args []string Args []string
Env map[string]string EnvironmentVariables map[string]string
WindowTitle string WindowTitle string
} }
// InputType identifies one backend-neutral keyboard, mouse, or timing action. // InputType identifies one backend-neutral keyboard, mouse, or timing action.
@@ -36,7 +36,8 @@ type InputAction struct {
DurationMS int `json:"duration_ms,omitempty"` DurationMS int `json:"duration_ms,omitempty"`
} }
// LogEntry is a snapshot of one application output stream. // LogEntry is a cumulative snapshot of one application output stream. Time is
// when Agentbox captured the snapshot, not when the application emitted it.
type LogEntry struct { type LogEntry struct {
Stream string `json:"stream"` Stream string `json:"stream"`
Message string `json:"message"` Message string `json:"message"`
+3 -1
View File
@@ -117,7 +117,7 @@ func Run(ctx context.Context) (runErr error) {
return fmt.Errorf("build environment image: %w", err) return fmt.Errorf("build environment image: %w", err)
} }
fmt.Println("Creating isolated graphical environment...") fmt.Println("Creating restricted graphical environment...")
_, err = r.docker(ctx, _, err = r.docker(ctx,
"create", "create",
"--name", r.containerName, "--name", r.containerName,
@@ -327,6 +327,8 @@ func squareCentroid(path string) (point, error) {
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ { for x := bounds.Min.X; x < bounds.Max.X; x++ {
red, green, blue, _ := img.At(x, y).RGBA() red, green, blue, _ := img.At(x, y).RGBA()
// Match the demo square's #00ff66 color with enough tolerance for
// image conversion while rejecting the black background.
if green > 0xc000 && red < 0x4000 && blue < 0x8000 { if green > 0xc000 && red < 0x4000 && blue < 0x8000 {
sumX += uint64(x) sumX += uint64(x)
sumY += uint64(y) sumY += uint64(y)
+1 -1
View File
@@ -45,7 +45,7 @@ func Run(ctx context.Context, config Config) (runErr error) {
if err := config.Environment.Launch(ctx, config.Command); err != nil { if err := config.Environment.Launch(ctx, config.Command); err != nil {
return err return err
} }
fmt.Fprintln(config.Output, "Agent attached.") fmt.Fprintln(config.Output, "Agent loop started.")
var stepHistory []agent.Step var stepHistory []agent.Step
var actionHistory []environment.InputAction var actionHistory []environment.InputAction
+4
View File
@@ -0,0 +1,4 @@
// Package trace stores durable run records. steps.jsonl contains complete
// observations and decisions, actions.jsonl is an action-only view, and PNG
// screenshots remain separate files referenced by relative paths.
package trace
+3
View File
@@ -25,6 +25,8 @@ func List(projectRoot string) ([]Run, error) {
continue continue
} }
run, err := Read(projectRoot, entry.Name()) run, err := Read(projectRoot, entry.Name())
// Old or malformed trace directories are intentionally hidden rather
// than making the entire run listing fail.
if err == nil && run.SchemaVersion == SchemaVersion { if err == nil && run.SchemaVersion == SchemaVersion {
runs = append(runs, run) runs = append(runs, run)
} }
@@ -87,6 +89,7 @@ func ReadSteps(projectRoot, runID string) ([]StepRecord, error) {
} }
func validateRunID(runID string) error { func validateRunID(runID string) error {
// Run IDs become path components, so reject separators and traversal.
if filepath.Base(runID) != runID { if filepath.Base(runID) != runID {
return errors.New("invalid run ID") return errors.New("invalid run ID")
} }