Add StartupNudge function for unified session metadata (gt-bgfqy)
Creates internal/session/startup.go with StartupNudgeConfig struct and StartupNudge function. Format becomes session title in /resume picker: [GAS TOWN] recipient <- sender • timestamp • topic[:mol-id] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
b020b2634e
commit
172d5f7402
67
internal/session/startup.go
Normal file
67
internal/session/startup.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
// Package session provides polecat session lifecycle management.
|
||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StartupNudgeConfig configures a startup nudge message.
|
||||||
|
type StartupNudgeConfig struct {
|
||||||
|
// Recipient is the address of the agent being nudged.
|
||||||
|
// Examples: "gastown/crew/gus", "deacon", "gastown/witness"
|
||||||
|
Recipient string
|
||||||
|
|
||||||
|
// Sender is the agent initiating the nudge.
|
||||||
|
// Examples: "mayor", "deacon", "self" (for handoff)
|
||||||
|
Sender string
|
||||||
|
|
||||||
|
// Topic describes why the session was started.
|
||||||
|
// Examples: "cold-start", "handoff", "assigned", or a mol-id
|
||||||
|
Topic string
|
||||||
|
|
||||||
|
// MolID is an optional molecule ID being worked.
|
||||||
|
// If provided, appended to topic as "topic:mol-id"
|
||||||
|
MolID string
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartupNudge sends a formatted startup message to a Claude Code session.
|
||||||
|
// The message becomes the session title in Claude Code's /resume picker,
|
||||||
|
// enabling workers to find predecessor sessions.
|
||||||
|
//
|
||||||
|
// Format: [GAS TOWN] <recipient> <- <sender> • <timestamp> • <topic[:mol-id]>
|
||||||
|
//
|
||||||
|
// Examples:
|
||||||
|
// - [GAS TOWN] gastown/crew/gus <- deacon • 2025-12-30T15:42 • assigned:gt-abc12
|
||||||
|
// - [GAS TOWN] deacon <- mayor • 2025-12-30T08:00 • cold-start
|
||||||
|
// - [GAS TOWN] gastown/witness <- self • 2025-12-30T14:00 • handoff
|
||||||
|
//
|
||||||
|
// The message content doesn't trigger GUPP - CLAUDE.md and hooks handle that.
|
||||||
|
// The metadata makes sessions identifiable in /resume.
|
||||||
|
func StartupNudge(t *tmux.Tmux, session string, cfg StartupNudgeConfig) error {
|
||||||
|
message := FormatStartupNudge(cfg)
|
||||||
|
return t.NudgeSession(session, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatStartupNudge builds the formatted startup nudge message.
|
||||||
|
// Separated from StartupNudge for testing and reuse.
|
||||||
|
func FormatStartupNudge(cfg StartupNudgeConfig) string {
|
||||||
|
// Use local time in compact format
|
||||||
|
timestamp := time.Now().Format("2006-01-02T15:04")
|
||||||
|
|
||||||
|
// Build topic string - append mol-id if provided
|
||||||
|
topic := cfg.Topic
|
||||||
|
if cfg.MolID != "" && cfg.Topic != "" {
|
||||||
|
topic = fmt.Sprintf("%s:%s", cfg.Topic, cfg.MolID)
|
||||||
|
} else if cfg.MolID != "" {
|
||||||
|
topic = cfg.MolID
|
||||||
|
} else if topic == "" {
|
||||||
|
topic = "ready"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the beacon: [GAS TOWN] recipient <- sender • timestamp • topic
|
||||||
|
return fmt.Sprintf("[GAS TOWN] %s <- %s • %s • %s",
|
||||||
|
cfg.Recipient, cfg.Sender, timestamp, topic)
|
||||||
|
}
|
||||||
103
internal/session/startup_test.go
Normal file
103
internal/session/startup_test.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package session
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormatStartupNudge(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
cfg StartupNudgeConfig
|
||||||
|
wantSub []string // substrings that must appear
|
||||||
|
wantNot []string // substrings that must NOT appear
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "assigned with mol-id",
|
||||||
|
cfg: StartupNudgeConfig{
|
||||||
|
Recipient: "gastown/crew/gus",
|
||||||
|
Sender: "deacon",
|
||||||
|
Topic: "assigned",
|
||||||
|
MolID: "gt-abc12",
|
||||||
|
},
|
||||||
|
wantSub: []string{
|
||||||
|
"[GAS TOWN]",
|
||||||
|
"gastown/crew/gus",
|
||||||
|
"<- deacon",
|
||||||
|
"assigned:gt-abc12",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cold-start no mol-id",
|
||||||
|
cfg: StartupNudgeConfig{
|
||||||
|
Recipient: "deacon",
|
||||||
|
Sender: "mayor",
|
||||||
|
Topic: "cold-start",
|
||||||
|
},
|
||||||
|
wantSub: []string{
|
||||||
|
"[GAS TOWN]",
|
||||||
|
"deacon",
|
||||||
|
"<- mayor",
|
||||||
|
"cold-start",
|
||||||
|
},
|
||||||
|
// No wantNot - timestamp contains ":"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "handoff self",
|
||||||
|
cfg: StartupNudgeConfig{
|
||||||
|
Recipient: "gastown/witness",
|
||||||
|
Sender: "self",
|
||||||
|
Topic: "handoff",
|
||||||
|
},
|
||||||
|
wantSub: []string{
|
||||||
|
"[GAS TOWN]",
|
||||||
|
"gastown/witness",
|
||||||
|
"<- self",
|
||||||
|
"handoff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mol-id only",
|
||||||
|
cfg: StartupNudgeConfig{
|
||||||
|
Recipient: "gastown/polecats/Toast",
|
||||||
|
Sender: "witness",
|
||||||
|
MolID: "gt-xyz99",
|
||||||
|
},
|
||||||
|
wantSub: []string{
|
||||||
|
"[GAS TOWN]",
|
||||||
|
"gastown/polecats/Toast",
|
||||||
|
"<- witness",
|
||||||
|
"gt-xyz99",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty topic defaults to ready",
|
||||||
|
cfg: StartupNudgeConfig{
|
||||||
|
Recipient: "deacon",
|
||||||
|
Sender: "mayor",
|
||||||
|
},
|
||||||
|
wantSub: []string{
|
||||||
|
"[GAS TOWN]",
|
||||||
|
"ready",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := FormatStartupNudge(tt.cfg)
|
||||||
|
|
||||||
|
for _, sub := range tt.wantSub {
|
||||||
|
if !strings.Contains(got, sub) {
|
||||||
|
t.Errorf("FormatStartupNudge() = %q, want to contain %q", got, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sub := range tt.wantNot {
|
||||||
|
if strings.Contains(got, sub) {
|
||||||
|
t.Errorf("FormatStartupNudge() = %q, should NOT contain %q", got, sub)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user