feat: Add Codex and OpenCode runtime backend support (#281)

Adds support for alternative AI runtime backends (Codex, OpenCode) alongside
the default Claude backend through a runtime abstraction layer.

- internal/runtime/runtime.go - Runtime-agnostic helper functions
- Extended RuntimeConfig with provider-specific settings
- internal/opencode/ for OpenCode plugin support
- Updated session managers to use runtime abstraction
- Removed unused ensureXxxSession functions
- Fixed daemon.go indentation, updated terminology to runtime

Backward compatible: Claude remains default runtime.

Co-Authored-By: Ben Kraus <ben@cinematicsoftware.com>
Co-Authored-By: Cameron Palmer <cameronmpalmer@users.noreply.github.com>
This commit is contained in:
george
2026-01-08 22:56:37 -08:00
committed by Steve Yegge
33 changed files with 850 additions and 176 deletions

View File

@@ -726,15 +726,7 @@ func LoadRuntimeConfig(rigPath string) *RuntimeConfig {
if settings.Runtime == nil {
return DefaultRuntimeConfig()
}
// Fill in defaults for empty fields
rc := settings.Runtime
if rc.Command == "" {
rc.Command = "claude"
}
if rc.Args == nil {
rc.Args = []string{"--dangerously-skip-permissions"}
}
return rc
return normalizeRuntimeConfig(settings.Runtime)
}
// TownSettingsPath returns the path to town settings file.
@@ -1080,14 +1072,22 @@ func BuildStartupCommand(envVars map[string]string, rigPath, prompt string) stri
}
}
// Copy env vars to avoid mutating caller map
resolvedEnv := make(map[string]string, len(envVars)+2)
for k, v := range envVars {
resolvedEnv[k] = v
}
// Add GT_ROOT so agents can find town-level resources (formulas, etc.)
if townRoot != "" {
envVars["GT_ROOT"] = townRoot
resolvedEnv["GT_ROOT"] = townRoot
}
if rc.Session != nil && rc.Session.SessionIDEnv != "" {
resolvedEnv["GT_SESSION_ID_ENV"] = rc.Session.SessionIDEnv
}
// Build environment export prefix
var exports []string
for k, v := range envVars {
for k, v := range resolvedEnv {
exports = append(exports, fmt.Sprintf("%s=%s", k, v))
}
@@ -1109,6 +1109,21 @@ func BuildStartupCommand(envVars map[string]string, rigPath, prompt string) stri
return cmd
}
// PrependEnv prepends export statements to a command string.
func PrependEnv(command string, envVars map[string]string) string {
if len(envVars) == 0 {
return command
}
var exports []string
for k, v := range envVars {
exports = append(exports, fmt.Sprintf("%s=%s", k, v))
}
sort.Strings(exports)
return "export " + strings.Join(exports, " ") + " && " + command
}
// BuildStartupCommandWithAgentOverride builds a startup command like BuildStartupCommand,
// but uses agentOverride if non-empty.
func BuildStartupCommandWithAgentOverride(envVars map[string]string, rigPath, prompt, agentOverride string) (string, error) {