internal/enrollment/preflight.go
Rama main · réplica de lectura del repositorio original.
package enrollment
import (
"context"
"os"
"strings"
"time"
"github.com/yeomyeonggeori/blueclaw/internal/store/postgres"
)
type CheckName string
const (
CheckDatabase CheckName = "postgres"
CheckLanguageModel CheckName = "language model"
CheckHarness CheckName = "harness"
CheckWorkspace CheckName = "workspace"
)
type CheckResult struct {
Name CheckName
IsReady bool
Detail string
Guidance string
}
const databaseCheckTimeout = 5 * time.Second
func Preflight(ctx context.Context, home Home, answers Answers) []CheckResult {
return []CheckResult{
checkDatabase(ctx, home, answers.DatabaseConnectionString),
checkLanguageModel(answers.LanguageModel),
checkHarness(answers.Harness),
checkWorkspace(answers.WorkspaceRootPath),
}
}
func checkDatabase(ctx context.Context, home Home, connectionString string) CheckResult {
if strings.TrimSpace(connectionString) == "" {
return CheckResult{Name: CheckDatabase, Guidance: "blueclaw keeps every task, event and memory in Postgres, so it needs a connection string."}
}
managedPostgres := NewManagedPostgres(home)
if connectionString == managedPostgres.ConnectionString() {
if errorValue := managedPostgres.EnsureRunning(ctx); errorValue != nil {
return CheckResult{Name: CheckDatabase, Detail: errorValue.Error(), Guidance: "Point this line at a Postgres you already run instead."}
}
return CheckResult{Name: CheckDatabase, IsReady: true, Detail: "managed by blueclaw at " + managedPostgres.DirectoryPath()}
}
connectContext, cancel := context.WithTimeout(ctx, databaseCheckTimeout)
defer cancel()
database, errorValue := postgres.OpenDatabase(connectContext, connectionString, 0)
if errorValue != nil {
return CheckResult{Name: CheckDatabase, Detail: errorValue.Error(), Guidance: "Start a Postgres and create a database blueclaw can reach, then correct this line."}
}
database.Close()
return CheckResult{Name: CheckDatabase, IsReady: true}
}
func checkLanguageModel(access LanguageModelAccess) CheckResult {
endpointURL := strings.TrimSpace(access.EndpointURL)
if endpointURL == "" {
return CheckResult{Name: CheckLanguageModel, Guidance: "Give blueclaw a model endpoint to ask, and a key for it if that endpoint wants one."}
}
modelName := strings.TrimSpace(access.ModelName)
if modelName == "" {
return CheckResult{Name: CheckLanguageModel, Guidance: "Name the model this endpoint serves, the way the endpoint spells it."}
}
return CheckResult{Name: CheckLanguageModel, IsReady: true, Detail: modelName + " at " + endpointURL}
}
func checkHarness(harness HarnessChoice) CheckResult {
if strings.TrimSpace(harness.Name) == "" || harness.Name == "bluecollar-acp" {
return CheckResult{Name: CheckHarness, IsReady: true, Detail: "bluecollar (built in)"}
}
commandPath := strings.TrimSpace(harness.AgentCommandPath)
if commandPath == "" {
return CheckResult{Name: CheckHarness, Guidance: "This harness runs a command, so blueclaw needs its path."}
}
if _, errorValue := os.Stat(commandPath); errorValue != nil {
return CheckResult{Name: CheckHarness, Detail: errorValue.Error(), Guidance: "Install that agent, or choose the built-in bluecollar harness."}
}
return CheckResult{Name: CheckHarness, IsReady: true, Detail: commandPath}
}
func checkWorkspace(workspaceRootPath string) CheckResult {
if strings.TrimSpace(workspaceRootPath) == "" {
return CheckResult{Name: CheckWorkspace, Guidance: "The agent's work has to live somewhere."}
}
if errorValue := os.MkdirAll(workspaceRootPath, 0o755); errorValue != nil {
return CheckResult{Name: CheckWorkspace, Detail: errorValue.Error(), Guidance: "Choose a directory this account may write to."}
}
return CheckResult{Name: CheckWorkspace, IsReady: true, Detail: workspaceRootPath}
}
func IsReadyToStart(checkResults []CheckResult) bool {
for _, checkResult := range checkResults {
if !checkResult.IsReady {
return false
}
}
return true
}