Town-level services (Mayor, Deacon) now use hq- prefix instead of gt-: - hq-mayor (was gt-mayor) - hq-deacon (was gt-deacon) This distinguishes town-level sessions from rig-level sessions which continue to use gt- prefix (gt-gastown-witness, gt-gastown-crew-max, etc). Changes: - session.MayorSessionName() returns "hq-mayor" - session.DeaconSessionName() returns "hq-deacon" - ParseSessionName() handles both hq- and gt- prefixes - categorizeSession() handles both prefixes - categorizeSessions() accepts both prefixes - Updated all tests and documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
103 lines
3.3 KiB
Go
103 lines
3.3 KiB
Go
// Package session provides polecat session lifecycle management.
|
|
package session
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// Prefix is the common prefix for rig-level Gas Town tmux sessions.
|
|
const Prefix = "gt-"
|
|
|
|
// HQPrefix is the prefix for town-level services (Mayor, Deacon).
|
|
const HQPrefix = "hq-"
|
|
|
|
// MayorSessionName returns the session name for the Mayor agent.
|
|
// One mayor per machine - multi-town requires containers/VMs for isolation.
|
|
func MayorSessionName() string {
|
|
return HQPrefix + "mayor"
|
|
}
|
|
|
|
// DeaconSessionName returns the session name for the Deacon agent.
|
|
// One deacon per machine - multi-town requires containers/VMs for isolation.
|
|
func DeaconSessionName() string {
|
|
return HQPrefix + "deacon"
|
|
}
|
|
|
|
// WitnessSessionName returns the session name for a rig's Witness agent.
|
|
func WitnessSessionName(rig string) string {
|
|
return fmt.Sprintf("%s%s-witness", Prefix, rig)
|
|
}
|
|
|
|
// RefinerySessionName returns the session name for a rig's Refinery agent.
|
|
func RefinerySessionName(rig string) string {
|
|
return fmt.Sprintf("%s%s-refinery", Prefix, rig)
|
|
}
|
|
|
|
// CrewSessionName returns the session name for a crew worker in a rig.
|
|
func CrewSessionName(rig, name string) string {
|
|
return fmt.Sprintf("%s%s-crew-%s", Prefix, rig, name)
|
|
}
|
|
|
|
// PolecatSessionName returns the session name for a polecat in a rig.
|
|
func PolecatSessionName(rig, name string) string {
|
|
return fmt.Sprintf("%s%s-%s", Prefix, rig, name)
|
|
}
|
|
|
|
// PropulsionNudge generates the GUPP (Gas Town Universal Propulsion Principle) nudge.
|
|
// This is sent after the beacon to trigger autonomous work execution.
|
|
// The agent receives this as user input, triggering the propulsion principle:
|
|
// "If work is on your hook, YOU RUN IT."
|
|
func PropulsionNudge() string {
|
|
return "Run `gt hook` to check your hook and begin work."
|
|
}
|
|
|
|
// PropulsionNudgeForRole generates a role-specific GUPP nudge.
|
|
// Different roles have different startup flows:
|
|
// - polecat/crew: Check hook for slung work
|
|
// - witness/refinery: Start patrol cycle
|
|
// - deacon: Start heartbeat patrol
|
|
// - mayor: Check mail for coordination work
|
|
//
|
|
// The workDir parameter is used to locate .runtime/session_id for including
|
|
// session ID in the message (for Claude Code /resume picker discovery).
|
|
func PropulsionNudgeForRole(role, workDir string) string {
|
|
var msg string
|
|
switch role {
|
|
case "polecat", "crew":
|
|
msg = PropulsionNudge()
|
|
case "witness":
|
|
msg = "Run `gt prime` to check patrol status and begin work."
|
|
case "refinery":
|
|
msg = "Run `gt prime` to check MQ status and begin patrol."
|
|
case "deacon":
|
|
msg = "Run `gt prime` to check patrol status and begin heartbeat cycle."
|
|
case "mayor":
|
|
msg = "Run `gt prime` to check mail and begin coordination."
|
|
default:
|
|
msg = PropulsionNudge()
|
|
}
|
|
|
|
// Append session ID if available (for /resume picker visibility)
|
|
if sessionID := readSessionID(workDir); sessionID != "" {
|
|
msg = fmt.Sprintf("%s [session:%s]", msg, sessionID)
|
|
}
|
|
return msg
|
|
}
|
|
|
|
// readSessionID reads the session ID from .runtime/session_id if it exists.
|
|
// Returns empty string if the file doesn't exist or can't be read.
|
|
func readSessionID(workDir string) string {
|
|
if workDir == "" {
|
|
return ""
|
|
}
|
|
sessionPath := filepath.Join(workDir, ".runtime", "session_id")
|
|
data, err := os.ReadFile(sessionPath)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(data))
|
|
}
|