96 lines
3.0 KiB
Go
96 lines
3.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// DaemonStatus captures daemon connection state for the current command
|
|
type DaemonStatus struct {
|
|
Mode string `json:"mode"` // "daemon" or "direct"
|
|
Connected bool `json:"connected"`
|
|
Degraded bool `json:"degraded"`
|
|
SocketPath string `json:"socket_path,omitempty"`
|
|
AutoStartEnabled bool `json:"auto_start_enabled"`
|
|
AutoStartAttempted bool `json:"auto_start_attempted"`
|
|
AutoStartSucceeded bool `json:"auto_start_succeeded"`
|
|
FallbackReason string `json:"fallback_reason,omitempty"` // "none","flag_no_daemon","connect_failed","health_failed","auto_start_disabled","auto_start_failed"
|
|
Detail string `json:"detail,omitempty"` // short diagnostic
|
|
Health string `json:"health,omitempty"` // "healthy","degraded","unhealthy"
|
|
}
|
|
|
|
// Fallback reason constants
|
|
const (
|
|
FallbackNone = "none"
|
|
FallbackFlagNoDaemon = "flag_no_daemon"
|
|
FallbackConnectFailed = "connect_failed"
|
|
FallbackHealthFailed = "health_failed"
|
|
FallbackWorktreeSafety = "worktree_safety"
|
|
FallbackSingleProcessOnly = "single_process_only"
|
|
cmdDaemon = "daemon"
|
|
cmdImport = "import"
|
|
statusHealthy = "healthy"
|
|
FallbackAutoStartDisabled = "auto_start_disabled"
|
|
FallbackAutoStartFailed = "auto_start_failed"
|
|
FallbackDaemonUnsupported = "daemon_unsupported"
|
|
FallbackWispOperation = "wisp_operation"
|
|
)
|
|
|
|
// Command group IDs for help organization
|
|
const (
|
|
GroupMaintenance = "maintenance"
|
|
GroupIntegrations = "integrations"
|
|
)
|
|
|
|
// signalOrchestratorActivity writes an activity signal for orchestrator daemon.
|
|
// This enables exponential backoff based on bd usage detection.
|
|
// Best-effort: silent on any failure, never affects bd operation.
|
|
func signalOrchestratorActivity() {
|
|
// Determine town root from environment
|
|
// Priority: GT_ROOT env > skip (no default path detection)
|
|
townRoot := os.Getenv("GT_ROOT")
|
|
if townRoot == "" {
|
|
return // Not in orchestrator environment, skip
|
|
}
|
|
|
|
// Ensure daemon directory exists
|
|
daemonDir := filepath.Join(townRoot, "daemon")
|
|
if err := os.MkdirAll(daemonDir, 0755); err != nil {
|
|
return
|
|
}
|
|
|
|
// Build command line from os.Args
|
|
cmdLine := strings.Join(os.Args, " ")
|
|
|
|
// Determine actor (uses git config user.name as default)
|
|
actorName := getActorWithGit()
|
|
|
|
// Build activity signal
|
|
activity := struct {
|
|
LastCommand string `json:"last_command"`
|
|
Actor string `json:"actor"`
|
|
Timestamp string `json:"timestamp"`
|
|
}{
|
|
LastCommand: cmdLine,
|
|
Actor: actorName,
|
|
Timestamp: time.Now().UTC().Format(time.RFC3339),
|
|
}
|
|
|
|
data, err := json.Marshal(activity)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
// Write atomically (write to temp, rename)
|
|
activityPath := filepath.Join(daemonDir, "activity.json")
|
|
tmpPath := activityPath + ".tmp"
|
|
// nolint:gosec // G306: 0644 is appropriate for a status file
|
|
if err := os.WriteFile(tmpPath, data, 0644); err != nil {
|
|
return
|
|
}
|
|
_ = os.Rename(tmpPath, activityPath)
|
|
}
|