refactor: consolidate agent env vars into config.AgentEnv
Create centralized AgentEnv function as single source of truth for all agent environment variables. All agents now consistently receive: - GT_ROLE, BD_ACTOR, GIT_AUTHOR_NAME (role identity) - GT_ROOT, BEADS_DIR (workspace paths) - GT_RIG, GT_POLECAT/GT_CREW (rig-specific identity) - BEADS_AGENT_NAME, BEADS_NO_DAEMON (beads config) - CLAUDE_CONFIG_DIR (optional account selection) Remove RoleEnvVars in favor of AgentEnvSimple wrapper. Remove IncludeBeadsEnv flag - beads env vars always included. Update all manager and cmd call sites to use AgentEnv. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
52b9a95f98
commit
e999ceb1c1
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/tmux"
|
||||
)
|
||||
@@ -190,9 +191,15 @@ func (b *Boot) spawnTmux() error {
|
||||
return fmt.Errorf("creating boot session: %w", err)
|
||||
}
|
||||
|
||||
// Set environment
|
||||
_ = b.tmux.SetEnvironment(SessionName, "GT_ROLE", "boot")
|
||||
_ = b.tmux.SetEnvironment(SessionName, "BD_ACTOR", "deacon-boot")
|
||||
// Set environment using centralized AgentEnv for consistency
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "boot",
|
||||
TownRoot: b.townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(b.townRoot),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = b.tmux.SetEnvironment(SessionName, k, v)
|
||||
}
|
||||
|
||||
// Launch Claude with environment exported inline and initial triage prompt
|
||||
// The "gt boot triage" prompt tells Boot to immediately start triage (GUPP principle)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
"github.com/steveyegge/gastown/internal/crew"
|
||||
@@ -138,13 +139,17 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_ROLE", "crew")
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", r.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
|
||||
// Set runtime config dir for account selection (non-fatal)
|
||||
if runtimeConfig.Session != nil && runtimeConfig.Session.ConfigDirEnv != "" && claudeConfigDir != "" {
|
||||
_ = t.SetEnvironment(sessionID, runtimeConfig.Session.ConfigDirEnv, claudeConfigDir)
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "crew",
|
||||
Rig: r.Name,
|
||||
AgentName: name,
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(r.Path),
|
||||
RuntimeConfigDir: claudeConfigDir,
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
}
|
||||
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
|
||||
@@ -358,8 +358,15 @@ func startDeaconSession(t *tmux.Tmux, sessionName, agentOverride string) error {
|
||||
}
|
||||
|
||||
// Set environment (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionName, "GT_ROLE", "deacon")
|
||||
_ = t.SetEnvironment(sessionName, "BD_ACTOR", "deacon")
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "deacon",
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(townRoot),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionName, k, v)
|
||||
}
|
||||
|
||||
// Apply Deacon theme (non-fatal: theming failure doesn't affect operation)
|
||||
// Note: ConfigureGasTownSession includes cycle bindings
|
||||
|
||||
@@ -506,7 +506,7 @@ func runRoleEnv(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Get canonical env vars from shared source of truth
|
||||
envVars := config.RoleEnvVars(string(info.Role), info.Rig, info.Polecat)
|
||||
envVars := config.AgentEnvSimple(string(info.Role), info.Rig, info.Polecat)
|
||||
envVars[EnvGTRoleHome] = home
|
||||
|
||||
// Output in sorted order for consistent output
|
||||
|
||||
204
internal/config/env.go
Normal file
204
internal/config/env.go
Normal file
@@ -0,0 +1,204 @@
|
||||
// Package config provides configuration loading and environment variable management.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// AgentEnvConfig specifies the configuration for generating agent environment variables.
|
||||
// This is the single source of truth for all agent environment configuration.
|
||||
type AgentEnvConfig struct {
|
||||
// Role is the agent role: mayor, deacon, witness, refinery, crew, polecat, boot
|
||||
Role string
|
||||
|
||||
// Rig is the rig name (empty for town-level agents like mayor/deacon)
|
||||
Rig string
|
||||
|
||||
// AgentName is the specific agent name (empty for singletons like witness/refinery)
|
||||
// For polecats, this is the polecat name. For crew, this is the crew member name.
|
||||
AgentName string
|
||||
|
||||
// TownRoot is the root of the Gas Town workspace.
|
||||
// Sets GT_ROOT environment variable.
|
||||
TownRoot string
|
||||
|
||||
// BeadsDir is the resolved BEADS_DIR path.
|
||||
// Callers should use beads.ResolveBeadsDir() to compute this.
|
||||
BeadsDir string
|
||||
|
||||
// RuntimeConfigDir is the optional CLAUDE_CONFIG_DIR path
|
||||
RuntimeConfigDir string
|
||||
|
||||
// BeadsNoDaemon sets BEADS_NO_DAEMON=1 if true
|
||||
// Used for polecats that should bypass the beads daemon
|
||||
BeadsNoDaemon bool
|
||||
}
|
||||
|
||||
// AgentEnv returns all environment variables for an agent based on the config.
|
||||
// This is the single source of truth for agent environment variables.
|
||||
func AgentEnv(cfg AgentEnvConfig) map[string]string {
|
||||
env := make(map[string]string)
|
||||
|
||||
env["GT_ROLE"] = cfg.Role
|
||||
|
||||
// Set role-specific variables
|
||||
switch cfg.Role {
|
||||
case "mayor":
|
||||
env["BD_ACTOR"] = "mayor"
|
||||
env["GIT_AUTHOR_NAME"] = "mayor"
|
||||
|
||||
case "deacon":
|
||||
env["BD_ACTOR"] = "deacon"
|
||||
env["GIT_AUTHOR_NAME"] = "deacon"
|
||||
|
||||
case "boot":
|
||||
env["BD_ACTOR"] = "deacon-boot"
|
||||
env["GIT_AUTHOR_NAME"] = "boot"
|
||||
|
||||
case "witness":
|
||||
env["GT_RIG"] = cfg.Rig
|
||||
env["BD_ACTOR"] = fmt.Sprintf("%s/witness", cfg.Rig)
|
||||
env["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/witness", cfg.Rig)
|
||||
|
||||
case "refinery":
|
||||
env["GT_RIG"] = cfg.Rig
|
||||
env["BD_ACTOR"] = fmt.Sprintf("%s/refinery", cfg.Rig)
|
||||
env["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/refinery", cfg.Rig)
|
||||
|
||||
case "polecat":
|
||||
env["GT_RIG"] = cfg.Rig
|
||||
env["GT_POLECAT"] = cfg.AgentName
|
||||
env["BD_ACTOR"] = fmt.Sprintf("%s/polecats/%s", cfg.Rig, cfg.AgentName)
|
||||
env["GIT_AUTHOR_NAME"] = cfg.AgentName
|
||||
|
||||
case "crew":
|
||||
env["GT_RIG"] = cfg.Rig
|
||||
env["GT_CREW"] = cfg.AgentName
|
||||
env["BD_ACTOR"] = fmt.Sprintf("%s/crew/%s", cfg.Rig, cfg.AgentName)
|
||||
env["GIT_AUTHOR_NAME"] = cfg.AgentName
|
||||
}
|
||||
|
||||
env["GT_ROOT"] = cfg.TownRoot
|
||||
env["BEADS_DIR"] = cfg.BeadsDir
|
||||
|
||||
// Set BEADS_AGENT_NAME for polecat/crew (uses same format as BD_ACTOR)
|
||||
if cfg.Role == "polecat" || cfg.Role == "crew" {
|
||||
env["BEADS_AGENT_NAME"] = fmt.Sprintf("%s/%s", cfg.Rig, cfg.AgentName)
|
||||
}
|
||||
|
||||
if cfg.BeadsNoDaemon {
|
||||
env["BEADS_NO_DAEMON"] = "1"
|
||||
}
|
||||
|
||||
// Add optional runtime config directory
|
||||
if cfg.RuntimeConfigDir != "" {
|
||||
env["CLAUDE_CONFIG_DIR"] = cfg.RuntimeConfigDir
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
|
||||
// AgentEnvSimple is a convenience function for simple role-based env var lookup.
|
||||
// Use this when you only need role, rig, and agentName without advanced options.
|
||||
func AgentEnvSimple(role, rig, agentName string) map[string]string {
|
||||
return AgentEnv(AgentEnvConfig{
|
||||
Role: role,
|
||||
Rig: rig,
|
||||
AgentName: agentName,
|
||||
})
|
||||
}
|
||||
|
||||
// ExportPrefix builds an export statement prefix for shell commands.
|
||||
// Returns a string like "export GT_ROLE=mayor BD_ACTOR=mayor && "
|
||||
// The keys are sorted for deterministic output.
|
||||
func ExportPrefix(env map[string]string) string {
|
||||
if len(env) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Sort keys for deterministic output
|
||||
keys := make([]string, 0, len(env))
|
||||
for k := range env {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var parts []string
|
||||
for _, k := range keys {
|
||||
parts = append(parts, fmt.Sprintf("%s=%s", k, env[k]))
|
||||
}
|
||||
|
||||
return "export " + strings.Join(parts, " ") + " && "
|
||||
}
|
||||
|
||||
// BuildStartupCommandWithEnv builds a startup command with the given environment variables.
|
||||
// This combines the export prefix with the agent command and optional prompt.
|
||||
func BuildStartupCommandWithEnv(env map[string]string, agentCmd, prompt string) string {
|
||||
prefix := ExportPrefix(env)
|
||||
|
||||
if prompt != "" {
|
||||
// Include prompt as argument to agent command
|
||||
return fmt.Sprintf("%s%s %q", prefix, agentCmd, prompt)
|
||||
}
|
||||
return prefix + agentCmd
|
||||
}
|
||||
|
||||
// MergeEnv merges multiple environment maps, with later maps taking precedence.
|
||||
func MergeEnv(maps ...map[string]string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, m := range maps {
|
||||
for k, v := range m {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// FilterEnv returns a new map with only the specified keys.
|
||||
func FilterEnv(env map[string]string, keys ...string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
for _, k := range keys {
|
||||
if v, ok := env[k]; ok {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// WithoutEnv returns a new map without the specified keys.
|
||||
func WithoutEnv(env map[string]string, keys ...string) map[string]string {
|
||||
result := make(map[string]string)
|
||||
exclude := make(map[string]bool)
|
||||
for _, k := range keys {
|
||||
exclude[k] = true
|
||||
}
|
||||
for k, v := range env {
|
||||
if !exclude[k] {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EnvForExecCommand returns os.Environ() with the given env vars appended.
|
||||
// This is useful for setting cmd.Env on exec.Command.
|
||||
func EnvForExecCommand(env map[string]string) []string {
|
||||
result := os.Environ()
|
||||
for k, v := range env {
|
||||
result = append(result, k+"="+v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// EnvToSlice converts an env map to a slice of "K=V" strings.
|
||||
// Useful for appending to os.Environ() manually.
|
||||
func EnvToSlice(env map[string]string) []string {
|
||||
result := make([]string, 0, len(env))
|
||||
for k, v := range env {
|
||||
result = append(result, k+"="+v)
|
||||
}
|
||||
return result
|
||||
}
|
||||
@@ -1170,45 +1170,6 @@ func BuildStartupCommandWithAgentOverride(envVars map[string]string, rigPath, pr
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// RoleEnvVars returns the canonical environment variables for a role.
|
||||
// This is the single source of truth for role identity env vars.
|
||||
// The role parameter should be one of: "mayor", "deacon", "witness", "refinery", "polecat", "crew".
|
||||
// For rig-specific roles, rig must be provided.
|
||||
// For polecat/crew, polecatOrCrew must be the polecat or crew member name.
|
||||
func RoleEnvVars(role, rig, polecatOrCrew string) map[string]string {
|
||||
envVars := map[string]string{
|
||||
"GT_ROLE": role,
|
||||
}
|
||||
|
||||
switch role {
|
||||
case "mayor":
|
||||
envVars["BD_ACTOR"] = "mayor"
|
||||
envVars["GIT_AUTHOR_NAME"] = "mayor"
|
||||
case "deacon":
|
||||
envVars["BD_ACTOR"] = "deacon"
|
||||
envVars["GIT_AUTHOR_NAME"] = "deacon"
|
||||
case "witness":
|
||||
envVars["GT_RIG"] = rig
|
||||
envVars["BD_ACTOR"] = fmt.Sprintf("%s/witness", rig)
|
||||
envVars["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/witness", rig)
|
||||
case "refinery":
|
||||
envVars["GT_RIG"] = rig
|
||||
envVars["BD_ACTOR"] = fmt.Sprintf("%s/refinery", rig)
|
||||
envVars["GIT_AUTHOR_NAME"] = fmt.Sprintf("%s/refinery", rig)
|
||||
case "polecat":
|
||||
envVars["GT_RIG"] = rig
|
||||
envVars["GT_POLECAT"] = polecatOrCrew
|
||||
envVars["BD_ACTOR"] = fmt.Sprintf("%s/polecats/%s", rig, polecatOrCrew)
|
||||
envVars["GIT_AUTHOR_NAME"] = polecatOrCrew
|
||||
case "crew":
|
||||
envVars["GT_RIG"] = rig
|
||||
envVars["GT_CREW"] = polecatOrCrew
|
||||
envVars["BD_ACTOR"] = fmt.Sprintf("%s/crew/%s", rig, polecatOrCrew)
|
||||
envVars["GIT_AUTHOR_NAME"] = polecatOrCrew
|
||||
}
|
||||
|
||||
return envVars
|
||||
}
|
||||
|
||||
// BuildAgentStartupCommand is a convenience function for starting agent sessions.
|
||||
// It sets standard environment variables (GT_ROLE, BD_ACTOR, GIT_AUTHOR_NAME)
|
||||
@@ -1235,23 +1196,23 @@ func BuildAgentStartupCommandWithAgentOverride(role, bdActor, rigPath, prompt, a
|
||||
// BuildPolecatStartupCommand builds the startup command for a polecat.
|
||||
// Sets GT_ROLE, GT_RIG, GT_POLECAT, BD_ACTOR, and GIT_AUTHOR_NAME.
|
||||
func BuildPolecatStartupCommand(rigName, polecatName, rigPath, prompt string) string {
|
||||
return BuildStartupCommand(RoleEnvVars("polecat", rigName, polecatName), rigPath, prompt)
|
||||
return BuildStartupCommand(AgentEnvSimple("polecat", rigName, polecatName), rigPath, prompt)
|
||||
}
|
||||
|
||||
// BuildPolecatStartupCommandWithAgentOverride is like BuildPolecatStartupCommand, but uses agentOverride if non-empty.
|
||||
func BuildPolecatStartupCommandWithAgentOverride(rigName, polecatName, rigPath, prompt, agentOverride string) (string, error) {
|
||||
return BuildStartupCommandWithAgentOverride(RoleEnvVars("polecat", rigName, polecatName), rigPath, prompt, agentOverride)
|
||||
return BuildStartupCommandWithAgentOverride(AgentEnvSimple("polecat", rigName, polecatName), rigPath, prompt, agentOverride)
|
||||
}
|
||||
|
||||
// BuildCrewStartupCommand builds the startup command for a crew member.
|
||||
// Sets GT_ROLE, GT_RIG, GT_CREW, BD_ACTOR, and GIT_AUTHOR_NAME.
|
||||
func BuildCrewStartupCommand(rigName, crewName, rigPath, prompt string) string {
|
||||
return BuildStartupCommand(RoleEnvVars("crew", rigName, crewName), rigPath, prompt)
|
||||
return BuildStartupCommand(AgentEnvSimple("crew", rigName, crewName), rigPath, prompt)
|
||||
}
|
||||
|
||||
// BuildCrewStartupCommandWithAgentOverride is like BuildCrewStartupCommand, but uses agentOverride if non-empty.
|
||||
func BuildCrewStartupCommandWithAgentOverride(rigName, crewName, rigPath, prompt, agentOverride string) (string, error) {
|
||||
return BuildStartupCommandWithAgentOverride(RoleEnvVars("crew", rigName, crewName), rigPath, prompt, agentOverride)
|
||||
return BuildStartupCommandWithAgentOverride(AgentEnvSimple("crew", rigName, crewName), rigPath, prompt, agentOverride)
|
||||
}
|
||||
|
||||
// ExpectedPaneCommands returns tmux pane command names that indicate the runtime is running.
|
||||
|
||||
@@ -514,13 +514,18 @@ func (m *Manager) Start(name string, opts StartOptions) error {
|
||||
}
|
||||
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_RIG", m.rig.Name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_CREW", name)
|
||||
_ = t.SetEnvironment(sessionID, "GT_ROLE", "crew")
|
||||
|
||||
// Set CLAUDE_CONFIG_DIR for account selection (non-fatal)
|
||||
if opts.ClaudeConfigDir != "" {
|
||||
_ = t.SetEnvironment(sessionID, "CLAUDE_CONFIG_DIR", opts.ClaudeConfigDir)
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
townRoot := filepath.Dir(m.rig.Path)
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "crew",
|
||||
Rig: m.rig.Name,
|
||||
AgentName: name,
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.rig.Path),
|
||||
RuntimeConfigDir: opts.ClaudeConfigDir,
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
}
|
||||
|
||||
// Apply rig-based theming (non-fatal: theming failure doesn't affect operation)
|
||||
|
||||
@@ -847,8 +847,8 @@ func (d *Daemon) restartPolecatSession(rigName, polecatName, sessionName string)
|
||||
}
|
||||
|
||||
// Set environment variables
|
||||
// Use shared RoleEnvVars for consistency across all role startup paths
|
||||
envVars := config.RoleEnvVars("polecat", rigName, polecatName)
|
||||
// Use centralized AgentEnvSimple for consistency across all role startup paths
|
||||
envVars := config.AgentEnvSimple("polecat", rigName, polecatName)
|
||||
|
||||
// Add polecat-specific beads configuration
|
||||
// Use ResolveBeadsDir to follow redirects for repos with tracked beads
|
||||
|
||||
@@ -487,18 +487,31 @@ func (d *Daemon) getStartCommand(roleConfig *beads.RoleConfig, parsed *ParsedIde
|
||||
}
|
||||
|
||||
// setSessionEnvironment sets environment variables for the tmux session.
|
||||
// Uses role bead config if available, falls back to hardcoded defaults.
|
||||
func (d *Daemon) setSessionEnvironment(sessionName, identity string, config *beads.RoleConfig, parsed *ParsedIdentity) {
|
||||
// Always set GT_ROLE
|
||||
_ = d.tmux.SetEnvironment(sessionName, "GT_ROLE", identity)
|
||||
// Uses centralized AgentEnv for consistency, plus role bead custom env vars if available.
|
||||
func (d *Daemon) setSessionEnvironment(sessionName, identity string, roleConfig *beads.RoleConfig, parsed *ParsedIdentity) {
|
||||
// Determine beads dir based on role type
|
||||
var beadsPath string
|
||||
if parsed.RigName != "" {
|
||||
beadsPath = filepath.Join(d.config.TownRoot, parsed.RigName)
|
||||
} else {
|
||||
beadsPath = d.config.TownRoot
|
||||
}
|
||||
|
||||
// BD_ACTOR uses slashes instead of dashes for path-like identity
|
||||
bdActor := identityToBDActor(identity)
|
||||
_ = d.tmux.SetEnvironment(sessionName, "BD_ACTOR", bdActor)
|
||||
// Use centralized AgentEnv for base environment variables
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: parsed.RoleType,
|
||||
Rig: parsed.RigName,
|
||||
AgentName: parsed.AgentName,
|
||||
TownRoot: d.config.TownRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(beadsPath),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = d.tmux.SetEnvironment(sessionName, k, v)
|
||||
}
|
||||
|
||||
// Set any custom env vars from role config
|
||||
if config != nil {
|
||||
for k, v := range config.EnvVars {
|
||||
// Set any custom env vars from role config (bead-defined overrides)
|
||||
if roleConfig != nil {
|
||||
for k, v := range roleConfig.EnvVars {
|
||||
expanded := beads.ExpandRolePattern(v, d.config.TownRoot, parsed.RigName, parsed.AgentName, parsed.RoleType)
|
||||
_ = d.tmux.SetEnvironment(sessionName, k, expanded)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/claude"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
@@ -93,8 +94,15 @@ func (m *Manager) Start(agentOverride string) error {
|
||||
}
|
||||
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
_ = t.SetEnvironment(sessionID, "GT_ROLE", "deacon")
|
||||
_ = t.SetEnvironment(sessionID, "BD_ACTOR", "deacon")
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "deacon",
|
||||
TownRoot: m.townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.townRoot),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
}
|
||||
|
||||
// Apply Deacon theming (non-fatal: theming failure doesn't affect operation)
|
||||
theme := tmux.DeaconTheme()
|
||||
|
||||
@@ -101,7 +101,7 @@ func (c *EnvVarsCheck) Run(ctx *CheckContext) *CheckResult {
|
||||
}
|
||||
|
||||
// Get expected env vars based on role
|
||||
expected := config.RoleEnvVars(string(identity.Role), identity.Rig, identity.Name)
|
||||
expected := config.AgentEnvSimple(string(identity.Role), identity.Rig, identity.Name)
|
||||
|
||||
// Get actual tmux env vars
|
||||
actual, err := reader.GetAllEnvironment(sess)
|
||||
|
||||
@@ -83,7 +83,7 @@ func TestEnvVarsCheck_NonGasTownSessions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_MayorCorrect(t *testing.T) {
|
||||
expected := config.RoleEnvVars("mayor", "", "")
|
||||
expected := config.AgentEnvSimple("mayor", "", "")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"hq-mayor"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
@@ -114,7 +114,7 @@ func TestEnvVarsCheck_MayorMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_WitnessCorrect(t *testing.T) {
|
||||
expected := config.RoleEnvVars("witness", "myrig", "")
|
||||
expected := config.AgentEnvSimple("witness", "myrig", "")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"gt-myrig-witness"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
@@ -148,7 +148,7 @@ func TestEnvVarsCheck_WitnessMismatch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_RefineryCorrect(t *testing.T) {
|
||||
expected := config.RoleEnvVars("refinery", "myrig", "")
|
||||
expected := config.AgentEnvSimple("refinery", "myrig", "")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"gt-myrig-refinery"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
@@ -164,7 +164,7 @@ func TestEnvVarsCheck_RefineryCorrect(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_PolecatCorrect(t *testing.T) {
|
||||
expected := config.RoleEnvVars("polecat", "myrig", "Toast")
|
||||
expected := config.AgentEnvSimple("polecat", "myrig", "Toast")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"gt-myrig-Toast"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
@@ -198,7 +198,7 @@ func TestEnvVarsCheck_PolecatMissing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_CrewCorrect(t *testing.T) {
|
||||
expected := config.RoleEnvVars("crew", "myrig", "worker1")
|
||||
expected := config.AgentEnvSimple("crew", "myrig", "worker1")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"gt-myrig-crew-worker1"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
@@ -214,9 +214,9 @@ func TestEnvVarsCheck_CrewCorrect(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_MultipleSessions(t *testing.T) {
|
||||
mayorEnv := config.RoleEnvVars("mayor", "", "")
|
||||
witnessEnv := config.RoleEnvVars("witness", "rig1", "")
|
||||
polecatEnv := config.RoleEnvVars("polecat", "rig1", "Toast")
|
||||
mayorEnv := config.AgentEnvSimple("mayor", "", "")
|
||||
witnessEnv := config.AgentEnvSimple("witness", "rig1", "")
|
||||
polecatEnv := config.AgentEnvSimple("polecat", "rig1", "Toast")
|
||||
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"hq-mayor", "gt-rig1-witness", "gt-rig1-Toast"},
|
||||
@@ -238,7 +238,7 @@ func TestEnvVarsCheck_MultipleSessions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEnvVarsCheck_MixedCorrectAndMismatch(t *testing.T) {
|
||||
mayorEnv := config.RoleEnvVars("mayor", "", "")
|
||||
mayorEnv := config.AgentEnvSimple("mayor", "", "")
|
||||
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"hq-mayor", "gt-rig1-witness"},
|
||||
@@ -297,7 +297,7 @@ func TestEnvVarsCheck_GetEnvError(t *testing.T) {
|
||||
|
||||
func TestEnvVarsCheck_HyphenatedRig(t *testing.T) {
|
||||
// Test rig name with hyphens: "foo-bar"
|
||||
expected := config.RoleEnvVars("witness", "foo-bar", "")
|
||||
expected := config.AgentEnvSimple("witness", "foo-bar", "")
|
||||
reader := &mockEnvReader{
|
||||
sessions: []string{"gt-foo-bar-witness"},
|
||||
sessionEnvs: map[string]map[string]string{
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/claude"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
@@ -94,8 +95,12 @@ func (m *Manager) Start(agentOverride string) error {
|
||||
}
|
||||
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
// Use shared RoleEnvVars for consistency across all role startup paths
|
||||
envVars := config.RoleEnvVars("mayor", "", "")
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "mayor",
|
||||
TownRoot: m.townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.townRoot),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
}
|
||||
|
||||
@@ -186,21 +186,21 @@ func (m *SessionManager) Start(polecat string, opts SessionStartOptions) error {
|
||||
}
|
||||
|
||||
// Set environment (non-fatal: session works without these)
|
||||
debugSession("SetEnvironment GT_RIG", m.tmux.SetEnvironment(sessionID, "GT_RIG", m.rig.Name))
|
||||
debugSession("SetEnvironment GT_POLECAT", m.tmux.SetEnvironment(sessionID, "GT_POLECAT", polecat))
|
||||
|
||||
// Set runtime config dir for account selection (non-fatal)
|
||||
if runtimeConfig.Session != nil && runtimeConfig.Session.ConfigDirEnv != "" && opts.RuntimeConfigDir != "" {
|
||||
debugSession("SetEnvironment "+runtimeConfig.Session.ConfigDirEnv, m.tmux.SetEnvironment(sessionID, runtimeConfig.Session.ConfigDirEnv, opts.RuntimeConfigDir))
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
townRoot := filepath.Dir(m.rig.Path)
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "polecat",
|
||||
Rig: m.rig.Name,
|
||||
AgentName: polecat,
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.rig.Path),
|
||||
RuntimeConfigDir: opts.RuntimeConfigDir,
|
||||
BeadsNoDaemon: true,
|
||||
})
|
||||
for k, v := range envVars {
|
||||
debugSession("SetEnvironment "+k, m.tmux.SetEnvironment(sessionID, k, v))
|
||||
}
|
||||
|
||||
// Set beads environment for worktree polecats (non-fatal)
|
||||
// Use ResolveBeadsDir to follow redirects for repos with tracked beads
|
||||
beadsDir := beads.ResolveBeadsDir(m.rig.Path)
|
||||
debugSession("SetEnvironment BEADS_DIR", m.tmux.SetEnvironment(sessionID, "BEADS_DIR", beadsDir))
|
||||
debugSession("SetEnvironment BEADS_NO_DAEMON", m.tmux.SetEnvironment(sessionID, "BEADS_NO_DAEMON", "1"))
|
||||
debugSession("SetEnvironment BEADS_AGENT_NAME", m.tmux.SetEnvironment(sessionID, "BEADS_AGENT_NAME", fmt.Sprintf("%s/%s", m.rig.Name, polecat)))
|
||||
|
||||
// Hook the issue to the polecat if provided via --issue flag
|
||||
if opts.Issue != "" {
|
||||
agentID := fmt.Sprintf("%s/polecats/%s", m.rig.Name, polecat)
|
||||
|
||||
@@ -186,19 +186,19 @@ func (m *Manager) Start(foreground bool) error {
|
||||
}
|
||||
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
// Use shared RoleEnvVars for consistency across all role startup paths
|
||||
envVars := config.RoleEnvVars("refinery", m.rig.Name, "")
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
townRoot := filepath.Dir(m.rig.Path)
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "refinery",
|
||||
Rig: m.rig.Name,
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.rig.Path),
|
||||
BeadsNoDaemon: true,
|
||||
})
|
||||
|
||||
// Add refinery-specific flag
|
||||
envVars["GT_REFINERY"] = "1"
|
||||
|
||||
// Add beads environment - refinery uses rig-level beads
|
||||
// Use ResolveBeadsDir to handle both tracked (mayor/rig) and local beads
|
||||
beadsDir := beads.ResolveBeadsDir(m.rig.Path)
|
||||
envVars["BEADS_DIR"] = beadsDir
|
||||
envVars["BEADS_NO_DAEMON"] = "1"
|
||||
envVars["BEADS_AGENT_NAME"] = envVars["BD_ACTOR"]
|
||||
|
||||
// Set all env vars in tmux session (for debugging) and they'll also be exported to Claude
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/gastown/internal/agent"
|
||||
"github.com/steveyegge/gastown/internal/beads"
|
||||
"github.com/steveyegge/gastown/internal/claude"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/constants"
|
||||
@@ -164,8 +165,14 @@ func (m *Manager) Start(foreground bool) error {
|
||||
}
|
||||
|
||||
// Set environment variables (non-fatal: session works without these)
|
||||
// Use shared RoleEnvVars for consistency across all role startup paths
|
||||
envVars := config.RoleEnvVars("witness", m.rig.Name, "")
|
||||
// Use centralized AgentEnv for consistency across all role startup paths
|
||||
townRoot := filepath.Dir(m.rig.Path)
|
||||
envVars := config.AgentEnv(config.AgentEnvConfig{
|
||||
Role: "witness",
|
||||
Rig: m.rig.Name,
|
||||
TownRoot: townRoot,
|
||||
BeadsDir: beads.ResolveBeadsDir(m.rig.Path),
|
||||
})
|
||||
for k, v := range envVars {
|
||||
_ = t.SetEnvironment(sessionID, k, v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user