Why: Headless runs need the pi tool names, walked context files, prompt includes, and a trust gate before project settings are loaded. Evidence: go test -count=1 ./runtime/tools/ ./runtime/resources/ ./runtime/skills/ Impact: Existing tool names stay registered. Aliases are added only when the underlying tool exists. Refs: feature/pi-headless N4 Co-authored-by: Cursor <cursoragent@cursor.com>
103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
package resources
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// Trust is how a project directory is treated in non-interactive runs.
|
|
type Trust string
|
|
|
|
const (
|
|
TrustAsk Trust = "ask"
|
|
TrustAlways Trust = "always"
|
|
TrustNever Trust = "never"
|
|
)
|
|
|
|
// Settings is the global plus project configuration.
|
|
type Settings struct {
|
|
Trust Trust `json:"trust,omitempty"`
|
|
Model string `json:"model,omitempty"`
|
|
Thinking string `json:"thinking,omitempty"`
|
|
Tools []string `json:"tools,omitempty"`
|
|
Exclude []string `json:"exclude,omitempty"`
|
|
Vars map[string]string `json:"vars,omitempty"`
|
|
}
|
|
|
|
// LoadSettings reads global (optional) then project settings.
|
|
// Project keys override global keys. A missing file is an empty settings.
|
|
func LoadSettings(globalPath, projectPath string) (Settings, error) {
|
|
global, err := readSettings(globalPath)
|
|
if err != nil {
|
|
return Settings{}, err
|
|
}
|
|
project, err := readSettings(projectPath)
|
|
if err != nil {
|
|
return Settings{}, err
|
|
}
|
|
return mergeSettings(global, project), nil
|
|
}
|
|
|
|
// ProjectTrusted reports whether project resources may be loaded.
|
|
// ask and never refuse in non-interactive mode. approve forces trust.
|
|
func ProjectTrusted(trust Trust, approve, noApprove bool) bool {
|
|
if noApprove {
|
|
return false
|
|
}
|
|
if approve {
|
|
return true
|
|
}
|
|
return trust == TrustAlways
|
|
}
|
|
|
|
func readSettings(path string) (Settings, error) {
|
|
if path == "" {
|
|
return Settings{}, nil
|
|
}
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return Settings{}, nil
|
|
}
|
|
return Settings{}, err
|
|
}
|
|
var s Settings
|
|
if err := json.Unmarshal(raw, &s); err != nil {
|
|
return Settings{}, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func mergeSettings(base, over Settings) Settings {
|
|
if over.Trust != "" {
|
|
base.Trust = over.Trust
|
|
}
|
|
if over.Model != "" {
|
|
base.Model = over.Model
|
|
}
|
|
if over.Thinking != "" {
|
|
base.Thinking = over.Thinking
|
|
}
|
|
if over.Tools != nil {
|
|
base.Tools = over.Tools
|
|
}
|
|
if over.Exclude != nil {
|
|
base.Exclude = over.Exclude
|
|
}
|
|
if len(over.Vars) > 0 {
|
|
if base.Vars == nil {
|
|
base.Vars = map[string]string{}
|
|
}
|
|
for k, v := range over.Vars {
|
|
base.Vars[k] = v
|
|
}
|
|
}
|
|
return base
|
|
}
|
|
|
|
// DefaultPaths returns the global and project settings paths for a cwd.
|
|
func DefaultPaths(home, cwd string) (string, string) {
|
|
return filepath.Join(home, ".agent-native", "settings.json"), filepath.Join(cwd, ".agent-native", "settings.json")
|
|
}
|