feat: runtime-aware tmux agent checks
This commit is contained in:
@@ -150,8 +150,8 @@ func runLiveCosts() error {
|
||||
// Extract cost from content
|
||||
cost := extractCost(content)
|
||||
|
||||
// Check if Claude is running
|
||||
running := t.IsClaudeRunning(session)
|
||||
// Check if an agent appears to be running
|
||||
running := t.IsAgentRunning(session)
|
||||
|
||||
costs = append(costs, SessionCost{
|
||||
Session: session,
|
||||
@@ -428,7 +428,6 @@ func extractCost(content string) float64 {
|
||||
return cost
|
||||
}
|
||||
|
||||
|
||||
func outputCostsJSON(output CostsOutput) error {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
@@ -89,9 +89,9 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
if !hasSession {
|
||||
existingSessions, err := t.FindSessionByWorkDir(worker.ClonePath, true)
|
||||
if err == nil && len(existingSessions) > 0 {
|
||||
// Found an existing session with Claude running in this directory
|
||||
// Found an existing session with an agent running in this directory
|
||||
existingSession := existingSessions[0]
|
||||
fmt.Printf("%s Found existing Claude session '%s' in crew directory\n",
|
||||
fmt.Printf("%s Found existing agent session '%s' in crew directory\n",
|
||||
style.Warning.Render("⚠"),
|
||||
existingSession)
|
||||
fmt.Printf(" Attaching to existing session instead of creating a new one\n")
|
||||
@@ -164,7 +164,11 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
||||
// Session exists - check if Claude is still running
|
||||
// Uses both pane command check and UI marker detection to avoid
|
||||
// restarting when user is in a subshell spawned from Claude
|
||||
if !t.IsClaudeRunning(sessionID) {
|
||||
agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, r.Path, crewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving agent: %w", err)
|
||||
}
|
||||
if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) {
|
||||
// Claude has exited, restart it using respawn-pane
|
||||
fmt.Printf("Claude exited, restarting...\n")
|
||||
|
||||
|
||||
@@ -447,13 +447,13 @@ func runSling(cmd *cobra.Command, args []string) error {
|
||||
if targetPane == "" {
|
||||
fmt.Printf("%s No pane to nudge (agent will discover work via gt prime)\n", style.Dim.Render("○"))
|
||||
} else {
|
||||
// Ensure Claude is ready before nudging (prevents race condition where
|
||||
// Ensure agent is ready before nudging (prevents race condition where
|
||||
// message arrives before Claude has fully started - see issue #115)
|
||||
sessionName := getSessionFromPane(targetPane)
|
||||
if sessionName != "" {
|
||||
if err := ensureClaudeReady(sessionName); err != nil {
|
||||
if err := ensureAgentReady(sessionName); err != nil {
|
||||
// Non-fatal: warn and continue, agent will discover work via gt prime
|
||||
fmt.Printf("%s Could not verify Claude ready: %v\n", style.Dim.Render("○"), err)
|
||||
fmt.Printf("%s Could not verify agent ready: %v\n", style.Dim.Render("○"), err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,30 +605,32 @@ func getSessionFromPane(pane string) string {
|
||||
return pane
|
||||
}
|
||||
|
||||
// ensureClaudeReady waits for Claude to be ready before nudging an existing session.
|
||||
// Uses the same pragmatic approach as session.Start(): poll for node process,
|
||||
// accept bypass dialog if present, then wait for full initialization.
|
||||
// Returns early if Claude is already running and ready.
|
||||
func ensureClaudeReady(sessionName string) error {
|
||||
// ensureAgentReady waits for an agent to be ready before nudging an existing session.
|
||||
// Uses a pragmatic approach: wait for the pane to leave a shell, then (Claude-only)
|
||||
// accept the bypass permissions warning and give it a moment to finish initializing.
|
||||
func ensureAgentReady(sessionName string) error {
|
||||
t := tmux.NewTmux()
|
||||
|
||||
// If Claude is already running, assume it's ready (session was started earlier)
|
||||
if t.IsClaudeRunning(sessionName) {
|
||||
// If an agent is already running, assume it's ready (session was started earlier)
|
||||
if t.IsAgentRunning(sessionName) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Claude not running yet - wait for it to start (shell → node transition)
|
||||
// Agent not running yet - wait for it to start (shell → program transition)
|
||||
if err := t.WaitForCommand(sessionName, constants.SupportedShells, constants.ClaudeStartTimeout); err != nil {
|
||||
return fmt.Errorf("waiting for Claude to start: %w", err)
|
||||
return fmt.Errorf("waiting for agent to start: %w", err)
|
||||
}
|
||||
|
||||
// Accept bypass permissions warning if present
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
// Claude-only: accept bypass permissions warning if present
|
||||
if t.IsClaudeRunning(sessionName) {
|
||||
_ = t.AcceptBypassPermissionsWarning(sessionName)
|
||||
|
||||
// Wait for Claude to be fully ready at the prompt
|
||||
// PRAGMATIC APPROACH: Use fixed delay rather than detection.
|
||||
// Claude startup takes ~5-8 seconds on typical machines.
|
||||
time.Sleep(8 * time.Second)
|
||||
// PRAGMATIC APPROACH: fixed delay rather than prompt detection.
|
||||
// Claude startup takes ~5-8 seconds on typical machines.
|
||||
time.Sleep(8 * time.Second)
|
||||
} else {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -277,7 +277,8 @@ func startConfiguredCrew(t *tmux.Tmux, townRoot string) {
|
||||
sessionID := crewSessionName(r.Name, crewName)
|
||||
if running, _ := t.HasSession(sessionID); running {
|
||||
// Session exists - check if Claude is still running
|
||||
if !t.IsClaudeRunning(sessionID) {
|
||||
agentCfg := config.ResolveAgentConfig(townRoot, r.Path)
|
||||
if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) {
|
||||
// Claude has exited, restart it
|
||||
fmt.Printf(" %s %s/%s session exists, restarting Claude...\n", style.Dim.Render("○"), r.Name, crewName)
|
||||
claudeCmd := config.BuildCrewStartupCommand(r.Name, crewName, r.Path, "gt prime")
|
||||
@@ -800,7 +801,11 @@ func runStartCrew(cmd *cobra.Command, args []string) error {
|
||||
|
||||
if hasSession {
|
||||
// Session exists - check if Claude is still running
|
||||
if !t.IsClaudeRunning(sessionID) {
|
||||
agentCfg, _, err := config.ResolveAgentConfigWithOverride(townRoot, r.Path, startCrewAgentOverride)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving agent: %w", err)
|
||||
}
|
||||
if !t.IsAgentRunning(sessionID, config.ExpectedPaneCommands(agentCfg)...) {
|
||||
// Claude has exited, restart it with "gt prime" as initial prompt
|
||||
fmt.Printf("Session exists, restarting Claude...\n")
|
||||
startupCmd, err := config.BuildCrewStartupCommandWithAgentOverride(rigName, name, r.Path, "gt prime", startCrewAgentOverride)
|
||||
|
||||
Reference in New Issue
Block a user