Files
Agent-System/internal/controlcenter/tasks/settings.go
T
weichen b11cc64845 feat(control-center): P1 pipeline core — stage machine, decisions, artifacts, leases, SSE
- pipeline: 8-stage order, NextStage (testing skippable), gate pure
  functions (agents may never advance; only in_progress->awaiting_review)
- tasks: domain layer over cc_* tables — CRUD, claim/heartbeat/release
  leases, advance-request, decisions with strict suggestion validation,
  stage transitions, external submit, stage settings, overview, lease reaping
- server: agent + console routes, SSE broker broadcasting every event
  insert, 60s lease reaper goroutine, 1MB body limit, domain error mapping
- archtest: allowlist controlcenter/* and engineeringdashboard packages
2026-09-17 09:13:36 +00:00

125 lines
3.5 KiB
Go

package tasks
import (
"context"
"fmt"
"chat-agent-system/internal/controlcenter/pipeline"
"chat-agent-system/internal/controlcenter/store"
)
type StageSetting struct {
Scope string `json:"scope"`
TaskID *string `json:"task_id,omitempty"`
Stage string `json:"stage"`
Model string `json:"model"`
Executor string `json:"executor"`
}
// ListStageSettings returns global rows, or — when taskID is given — the
// task-level overrides merged on top of the globals, keyed by stage.
func ListStageSettings(ctx context.Context, st *store.Store, taskID string) ([]StageSetting, error) {
rows, err := st.Pool.Query(ctx,
`SELECT scope, task_id, stage, model, executor FROM cc_stage_settings ORDER BY stage`)
if err != nil {
return nil, err
}
defer rows.Close()
global := map[string]StageSetting{}
overrides := map[string]StageSetting{}
for rows.Next() {
var s StageSetting
if err := rows.Scan(&s.Scope, &s.TaskID, &s.Stage, &s.Model, &s.Executor); err != nil {
return nil, err
}
if s.Scope == "global" {
global[s.Stage] = s
} else if taskID != "" && s.TaskID != nil && *s.TaskID == taskID {
overrides[s.Stage] = s
}
}
if err := rows.Err(); err != nil {
return nil, err
}
out := []StageSetting{}
for _, stage := range pipeline.Stages {
if s, ok := overrides[stage]; ok {
out = append(out, s)
continue
}
if s, ok := global[stage]; ok {
out = append(out, s)
}
}
return out, nil
}
// ResolveStageSetting picks the effective setting for a stage: task override
// first, global fallback.
func ResolveStageSetting(ctx context.Context, st *store.Store, taskID, stage string) (*StageSetting, error) {
for _, s := range []struct {
scope string
taskID *string
}{
{"task", &taskID},
{"global", nil},
} {
var out StageSetting
var err error
if s.taskID == nil {
err = st.Pool.QueryRow(ctx,
`SELECT scope, task_id, stage, model, executor FROM cc_stage_settings
WHERE scope = 'global' AND stage = $1`, stage).
Scan(&out.Scope, &out.TaskID, &out.Stage, &out.Model, &out.Executor)
} else {
err = st.Pool.QueryRow(ctx,
`SELECT scope, task_id, stage, model, executor FROM cc_stage_settings
WHERE scope = 'task' AND task_id = $1 AND stage = $2`, *s.taskID, stage).
Scan(&out.Scope, &out.TaskID, &out.Stage, &out.Model, &out.Executor)
}
if err == nil {
return &out, nil
}
}
return nil, nil
}
// PutStageSetting upserts one stage -> {model, executor} mapping, globally or
// for a single task.
func PutStageSetting(ctx context.Context, st *store.Store, taskID *string, stage, model, executor string) error {
if !pipeline.ValidStage(stage) {
return fmt.Errorf("%w: invalid stage %q", ErrBadInput, stage)
}
if executor == "" {
executor = "agent"
}
switch executor {
case "agent", "human", "external":
default:
return fmt.Errorf("%w: executor must be agent, human or external", ErrBadInput)
}
scope := "global"
if taskID != nil && *taskID != "" {
scope = "task"
if _, err := GetTask(ctx, st, *taskID); err != nil {
return err
}
} else {
taskID = nil
}
res, err := st.Pool.Exec(ctx,
`UPDATE cc_stage_settings SET model = $4, executor = $5, updated_at = now()
WHERE scope = $1 AND stage = $3 AND (task_id IS NOT DISTINCT FROM $2)`,
scope, taskID, stage, model, executor)
if err != nil {
return err
}
if res.RowsAffected() > 0 {
return nil
}
_, err = st.Pool.Exec(ctx,
`INSERT INTO cc_stage_settings (scope, task_id, stage, model, executor) VALUES ($1,$2,$3,$4,$5)`,
scope, taskID, stage, model, executor)
return err
}