Ctrl K
ComunidadGuías…

internal/enrollment/preflight.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}