package harness import ( "encoding/json" "fmt" "os" ) type Scenario struct { Path string `json:"-"` Name string `json:"name,omitempty"` ProjectDir string `json:"project_dir,omitempty"` Cols uint16 `json:"cols,omitempty"` Rows uint16 `json:"rows,omitempty"` Env map[string]string `json:"env,omitempty"` Trust []string `json:"trust,omitempty"` Presets ScenarioPresets `json:"presets,omitempty"` Scripts []ScenarioScript `json:"scripts,omitempty"` Steps []Step `json:"steps"` } type ScenarioPresets struct { Agents []ScenarioPreset `json:"agents,omitempty"` Processes []ScenarioPreset `json:"processes,omitempty"` } type ScenarioPreset struct { Name string `json:"name"` Argv []string `json:"argv"` Env map[string]string `json:"env,omitempty"` WorkingDir string `json:"working_dir,omitempty"` Shell bool `json:"shell,omitempty"` IdleDetection *ScenarioIdleDetection `json:"idle_detection,omitempty"` } // ScenarioIdleDetection mirrors preset.IdleDetection so scenarios can // configure per-strategy idle detection for fake agent presets. type ScenarioIdleDetection struct { Strategy string `json:"strategy,omitempty"` IdleThresholdMS int `json:"idle_threshold_ms,omitempty"` TitleStatusMap map[string]string `json:"title_status_map,omitempty"` PermissionPatterns []string `json:"permission_patterns,omitempty"` ThinkingPatterns []string `json:"thinking_patterns,omitempty"` ErrorPatterns []string `json:"error_patterns,omitempty"` } type ScenarioScript struct { Name string `json:"name"` Body string `json:"body"` } type Step struct { Type string `json:"type"` Chord string `json:"chord,omitempty"` Text string `json:"text,omitempty"` Contains string `json:"contains,omitempty"` Regex string `json:"regex,omitempty"` TimeoutMS int `json:"timeout_ms,omitempty"` Method string `json:"method,omitempty"` Params json.RawMessage `json:"params,omitempty"` SaveAs string `json:"save_as,omitempty"` From string `json:"from,omitempty"` Path string `json:"path,omitempty"` Equals any `json:"equals,omitempty"` ErrorKind string `json:"error_kind,omitempty"` CursorRow *int `json:"cursor_row,omitempty"` CursorCol *int `json:"cursor_col,omitempty"` AllowSubstring bool `json:"allow_substring,omitempty"` } func LoadScenario(path string) (*Scenario, error) { b, err := os.ReadFile(path) if err != nil { return nil, err } var sc Scenario if err := json.Unmarshal(b, &sc); err != nil { return nil, fmt.Errorf("parse scenario %s: %w", path, err) } sc.Path = path if sc.Cols == 0 { sc.Cols = 120 } if sc.Rows == 0 { sc.Rows = 40 } return &sc, nil }