gito/services/core/cmd/worker/main.go
toki 518b8a6dcc feat(core): IOP 에이전트 런 브리지 구현 및 워커 확장
-runtime.go에 에이전트 런 브리지 인터op 기능 추가
- runner.go에 HTTP invoccer와 워커 브리지 로직 추가
- postgres.go에 worker_bridge 관련 migration 및 저장소 계층 구현
- model.go에 WorkerBridge 엔터티 모델 추가
- main.go에 워커 브리지 초기화 및 HTTP 서버 통합
- config.go에 워커 브리지 설정 항목 추가
- migration에 worker_bridge 스키마 추가
- agent-roadmap에 iop-agent-run-bridge 페이즈 및 마일스톤 업데이트
- http_invoker.go 새로 추가하여 외부 HTTP 호출 추상화
2026-06-17 19:59:04 +09:00

125 lines
4 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
"git.toki-labs.com/toki/gito/services/core/internal/config"
"git.toki-labs.com/toki/gito/services/core/internal/controlplane"
"git.toki-labs.com/toki/gito/services/core/internal/core"
"git.toki-labs.com/toki/gito/services/core/internal/storage"
"git.toki-labs.com/toki/gito/services/core/internal/worker"
)
// migrationSQL loads the initial migration SQL from well-known paths relative
// to the executable or common working directories, so the server/worker works
// regardless of the cwd it is launched from.
func migrationSQL() string {
exe, err := os.Executable()
if err != nil {
exe = ""
}
candidates := []string{
// set explicitly via env
os.Getenv("GITO_MIGRATION_PATH"),
// next to the binary (bin/gito-server → bin/../migrations/...)
filepath.Join(filepath.Dir(exe), "migrations", "00001_initial.sql"),
// launched from repo root
filepath.Join("services", "core", "migrations", "00001_initial.sql"),
// launched from services/core
filepath.Join("migrations", "00001_initial.sql"),
}
for _, p := range candidates {
if p == "" {
continue
}
data, err := os.ReadFile(p)
if err == nil {
return string(data)
}
}
return ""
}
// pgAgentRunInputLoader loads durable agent run inputs from the Postgres store
// and converts them to the worker input type.
type pgAgentRunInputLoader struct {
store storage.AgentRunInputStore
}
func (l *pgAgentRunInputLoader) LoadAgentRunInput(ctx context.Context, op core.Operation) (worker.AgentRunInput, error) {
durable, err := l.store.GetAgentRunInput(ctx, op.ID)
if err != nil {
if errors.Is(err, storage.ErrAgentRunInputNotFound) {
return worker.AgentRunInput{}, fmt.Errorf("agent run input not found for operation %s: %w", op.ID, err)
}
return worker.AgentRunInput{}, fmt.Errorf("load agent run input: %w", err)
}
if strings.TrimSpace(durable.WorkspacePath) == "" || strings.TrimSpace(durable.Branch) == "" || strings.TrimSpace(durable.Instruction) == "" || strings.TrimSpace(durable.ExpectedRevision) == "" {
return worker.AgentRunInput{}, fmt.Errorf("agent run input for %s is missing required fields (workspace_path, branch, instruction, expected_revision)", op.ID)
}
return worker.AgentRunInput{
OperationID: durable.OperationID,
RepoID: durable.RepoID,
Branch: durable.Branch,
WorkspacePath: durable.WorkspacePath,
Instruction: durable.Instruction,
PolicyContext: durable.PolicyContext,
ExpectedRevision: durable.ExpectedRevision,
}, nil
}
func main() {
cfg := config.Load()
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
dsn := os.Getenv("DATABASE_URL")
if dsn == "" {
logger.Error("DATABASE_URL is required")
os.Exit(1)
}
pgStore, err := storage.NewPgStore(context.Background(), dsn, migrationSQL())
if err != nil {
logger.Error("failed to open postgres store", "error", err)
os.Exit(1)
}
logger.Info("postgres store connected")
defer pgStore.Close()
publisher := controlplane.NewOutboxBroadcaster(pgStore.OperationEvents())
scanner := controlplane.NewRuntimeWithStore(publisher, pgStore)
iopEndpoint := strings.TrimSpace(cfg.IOPEndpoint)
if iopEndpoint == "" {
logger.Info("IOP_ENDPOINT not configured; agent_run picking disabled")
// Run with nil picker so worker logs the disabled message and exits cleanly.
w := worker.NewRunner(cfg, logger, nil, nil, scanner)
if err := w.Run(); err != nil {
logger.Error("worker stopped", "error", err)
os.Exit(1)
}
return
}
inputLoader := &pgAgentRunInputLoader{store: pgStore.AgentRunInputs()}
invoker := worker.NewHTTPInvoker(iopEndpoint)
w := worker.NewRunnerWithDependencies(cfg, logger, nil, nil, worker.RunnerDependencies{
OperationStore: pgStore.Operations(),
OperationEventStore: pgStore.OperationEvents(),
AgentRunPicker: pgStore.Operations(),
AgentRunInputLoader: inputLoader,
AgentRunInvoker: invoker,
RevisionScanner: scanner,
})
if err := w.Run(); err != nil {
logger.Error("worker stopped", "error", err)
os.Exit(1)
}
}