fix(agentci): resolve agents by Forgejo username, not config key

Adds FindByForgejoUser() to Spinner so dispatch matches issues
assigned to Forgejo users (Virgil, Claude, Charon) even when the
agent config key differs (e.g. Hypnos → forgejo_user: Claude).

Searches config key first (direct match), then ForgejoUser field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Claude 2026-02-10 03:07:07 +00:00
parent 886c67e560
commit e0619280fb
2 changed files with 24 additions and 5 deletions

View file

@ -61,6 +61,25 @@ func (s *Spinner) GetVerifierModel(agentName string) string {
return agent.VerifyModel
}
// FindByForgejoUser resolves a Forgejo username to the agent config key and config.
// This decouples agent naming (mythological roles) from Forgejo identity.
func (s *Spinner) FindByForgejoUser(forgejoUser string) (string, AgentConfig, bool) {
if forgejoUser == "" {
return "", AgentConfig{}, false
}
// Direct match on config key first.
if agent, ok := s.Agents[forgejoUser]; ok {
return forgejoUser, agent, true
}
// Search by ForgejoUser field.
for name, agent := range s.Agents {
if agent.ForgejoUser != "" && agent.ForgejoUser == forgejoUser {
return name, agent, true
}
}
return "", AgentConfig{}, false
}
// Weave compares primary and verifier outputs. Returns true if they converge.
// This is a placeholder for future semantic diff logic.
func (s *Spinner) Weave(ctx context.Context, primaryOutput, signedOutput []byte) (bool, error) {

View file

@ -68,12 +68,12 @@ func (h *DispatchHandler) Name() string {
}
// Match returns true for signals where a child issue needs coding (no PR yet)
// and the assignee is a known agent.
// and the assignee is a known agent (by config key or Forgejo username).
func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool {
if !signal.NeedsCoding {
return false
}
_, ok := h.spinner.Agents[signal.Assignee]
_, _, ok := h.spinner.FindByForgejoUser(signal.Assignee)
return ok
}
@ -81,7 +81,7 @@ func (h *DispatchHandler) Match(signal *jobrunner.PipelineSignal) bool {
func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.PipelineSignal) (*jobrunner.ActionResult, error) {
start := time.Now()
agent, ok := h.spinner.Agents[signal.Assignee]
agentName, agent, ok := h.spinner.FindByForgejoUser(signal.Assignee)
if !ok {
return nil, fmt.Errorf("unknown agent: %s", signal.Assignee)
}
@ -133,10 +133,10 @@ func (h *DispatchHandler) Execute(ctx context.Context, signal *jobrunner.Pipelin
}
// Clotho planning — determine execution mode.
runMode := h.spinner.DeterminePlan(signal, signal.Assignee)
runMode := h.spinner.DeterminePlan(signal, agentName)
verifyModel := ""
if runMode == agentci.ModeDual {
verifyModel = h.spinner.GetVerifierModel(signal.Assignee)
verifyModel = h.spinner.GetVerifierModel(agentName)
}
// Build ticket.