feat: Add rig-level custom agent support (#12)

* feat: Add rig-level custom agent support

Implement rig-level custom agent configuration support to enable per-rig
agent definitions in <rig>/settings/config.json, following the same pattern as
town-level agents in settings/config.json.

Changes:
- Added RigSettings.Agents field to internal/config/types.go
- Added DefaultRigAgentRegistryPath() and LoadRigAgentRegistry() functions to internal/config/agents.go
- Updated ResolveAgentConfigWithOverride() to accept and pass rigSettings parameter
- Updated GetRuntimeCommandWithAgentOverride() to use rigSettings when available
- Updated GetRuntimeCommandWithPromptAndAgentOverride() to use rigSettings
- Updated all Build*WithOverride functions to pass rigSettings

This fixes the issue where rig-level agent settings were loaded but
ignored by lookupAgentConfig, enabling per-rig custom agents for
polecats and crew members.

* test: Add rig-level custom agent tests

Added comprehensive unit tests for rig agent registry functions:
- TestDefaultRigAgentRegistryPath: verifies path construction
- TestLoadRigAgentRegistry: verifies file loading and JSON parsing
- TestLookupAgentConfigWithRigSettings: verifies agent lookup priority (rig > town > builtin)

Added placeholder integration test for future CI/CD setup.

* initial commit

* fix: resolve compilation errors in rig-level custom agent support

- Add missing RigAgentRegistryPath function (alias for DefaultRigAgentRegistryPath)
- Restore ResolveAgentConfigWithOverride function that was incorrectly removed
- Fix ResolveAgentConfig to return single value (not triple)
- Add initRegistryLocked() call to LoadRigAgentRegistry to prevent nil panic
- Fix DefaultRigAgentRegistryPath to use rigPath directly (not parent dir)
- Fix test file syntax errors (remove EOF artifacts)
- Fix test parameter order for lookupAgentConfig calls
- Fix test expectations to match correct custom agent override behavior

* test: implement rig-level custom agent integration test

- Add stub agent script that simulates AI agent with Q&A capability
- Test ResolveAgentConfig correctly picks up rig-level agents
- Test BuildPolecatStartupCommand includes custom agent command
- Test ResolveAgentConfigWithOverride respects rig agents
- Test rig agents override town agents with same name
- Add tmux integration test that spawns session and verifies output
- Stub agent echoes 'STUB_AGENT_STARTED' and handles ping/pong Q&A
- All tests pass including real tmux session verification

* docs: add OpenCode custom agent example to reference

- Show settings/agents.json format for advanced configs
- Include OpenCode example with session resume flags
- Document OPENCODE_PERMISSION env var for autonomous mode

* fix: improve rig-level agent support with docs and test fixes

- Add rig-level agent documentation to reference.md
- Document agent resolution order (rig → town → built-in)
- Deduplicate LoadAgentRegistry/LoadRigAgentRegistry into shared helper
- Fix test isolation in TestLoadRigAgentRegistry
- Fix nil pointer dereference in test assertions (use t.Fatal not t.Error)
This commit is contained in:
Subhrajit Makur
2026-01-08 01:23:29 +05:30
committed by Steve Yegge
parent 2de2d6b7e4
commit 00a59dec44
7 changed files with 746 additions and 20 deletions

View File

@@ -1570,3 +1570,94 @@ func TestSaveTownSettings(t *testing.T) {
}
})
}
// TestLookupAgentConfigWithRigSettings verifies that lookupAgentConfig checks
// rig-level agents first, then town-level agents, then built-ins.
func TestLookupAgentConfigWithRigSettings(t *testing.T) {
tests := []struct {
name string
rigSettings *RigSettings
townSettings *TownSettings
expectedCommand string
expectedFrom string
}{
{
name: "rig-custom-agent",
rigSettings: &RigSettings{
Agent: "default-rig-agent",
Agents: map[string]*RuntimeConfig{
"rig-custom-agent": {
Command: "custom-rig-cmd",
Args: []string{"--rig-flag"},
},
},
},
townSettings: &TownSettings{
Agents: map[string]*RuntimeConfig{
"town-custom-agent": {
Command: "custom-town-cmd",
Args: []string{"--town-flag"},
},
},
},
expectedCommand: "custom-rig-cmd",
expectedFrom: "rig",
},
{
name: "town-custom-agent",
rigSettings: &RigSettings{
Agents: map[string]*RuntimeConfig{
"other-rig-agent": {
Command: "other-rig-cmd",
},
},
},
townSettings: &TownSettings{
Agents: map[string]*RuntimeConfig{
"town-custom-agent": {
Command: "custom-town-cmd",
Args: []string{"--town-flag"},
},
},
},
expectedCommand: "custom-town-cmd",
expectedFrom: "town",
},
{
name: "unknown-agent",
rigSettings: nil,
townSettings: nil,
expectedCommand: "claude",
expectedFrom: "builtin",
},
{
name: "claude",
rigSettings: &RigSettings{
Agent: "claude",
},
townSettings: &TownSettings{
Agents: map[string]*RuntimeConfig{
"claude": {
Command: "custom-claude",
},
},
},
expectedCommand: "custom-claude",
expectedFrom: "town",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rc := lookupAgentConfig(tt.name, tt.townSettings, tt.rigSettings)
if rc == nil {
t.Errorf("lookupAgentConfig(%s) returned nil", tt.name)
}
if rc.Command != tt.expectedCommand {
t.Errorf("lookupAgentConfig(%s).Command = %s, want %s", tt.name, rc.Command, tt.expectedCommand)
}
})
}
}